--***********************************************************
--**                    THE INDIE STONE                    **
--**				  Author: turbotutone				   **
--***********************************************************

local function predicateBlowTorch(item)
	return item:getType() == "BlowTorch" and item:getDrainableUsesInt() >= 1
end

--test
local function lastIndexOf( _string, _needle)
	local i=_string:match(".*".._needle.."()")
	if i==nil then return nil else return i end
end

local function predicateNotBroken(item)
	return not item:isBroken()
end

--cache tables:
InfoPanelFlags = {};
SpriteGridCache = {}; -- a cache of a spritegrid object on world (stores associated sprites, object, squares)

ISMoveableSpriteProps = {};
ISMoveableSpriteProps.debug = false;
ISMoveableSpriteProps.itemInstances = {};
ISMoveableSpriteProps.multiSpriteFloorRadius = 3;
ISMoveableSpriteProps.ghc = getCore():getGoodHighlitedColor();
ISMoveableSpriteProps.bhc = getCore():getBadHighlitedColor();

function ISMoveableSpriteProps.fromObject( _object )
    local s = ISMoveableSpriteProps.new( _object and _object:getSprite() or nil );

    if s and s.sprite and s.spriteProps and _object then
        s.isFromObject = true;
        s.object = _object;
    end

    -- This is for dismantlable IsoThumpables without moveables properties, like stairs and lamp-on-pillar.
    if not s.canScrap and instanceof(_object, "IsoThumpable") and _object:isDismantable() then
        return ISThumpableSpriteProps.new(_object)
    end

    return s;
end

function ISMoveableSpriteProps.new( _sprite )
	local sprite = _sprite;
	local s = {}
	setmetatable( s, { __index = ISMoveableSpriteProps } );
	
	if _sprite == nil then return s end
	
	if type(sprite)=="string" then
		sprite = getSprite( sprite );
	end
	
	if sprite then
        s.sprite                        = sprite;
		local props 					= sprite:getProperties();
		if props then
            s.spriteProps               = props;

			s.spriteName 				= sprite:getName();
			s.blocksPlacement			= props:Is("BlocksPlacement");
			s.isHigh 					= props:Is("IsHigh");
			s.isLow 					= props:Is("IsLow");			
			s.isTable 					= props:Is("IsTable");
			s.isTableTop 				= props:Is("IsTableTop");
			s.isStackable				= props:Is("IsStackable");
			s.isWaterCollector			= props:Is("IsWaterCollector");
            s.material 					= props:Is("Material") and props:Val("Material");
            s.material2 				= props:Is("Material2") and props:Val("Material2")~="Undefined" and props:Val("Material2");
            s.material3 				= props:Is("Material3") and props:Val("Material3")~="Undefined" and props:Val("Material3");

            -- values for scrapping, scrapables are not always valid movables.
            s.canScrap                  = props:Is("CanScrap");
            if s.canScrap then
                if s.material and s.material ~= "Undefined" then
                    s.scrapUseTool      = not props:Is("ScrapUseTool");
                    s.scrapUseSkill     = not props:Is("ScrapUseSkill");
                    s.scrapSize			= props:Val("ScrapSize") or "Medium";
                    s.scrapToolUses     = props:Is("ScrapToolUseOverride") and props:Val("ScrapToolUseOverride");
                    if not s.scrapToolUses or s.scrapToolUses < 0 then
                        if s.scrapSize=="Small" then
                            s.scrapToolUses = 3;
                        elseif s.scrapSize=="Large" then
                            s.scrapToolUses = 10;
                        else -- Medium default
                            s.scrapToolUses = 5;
                        end
                    end
                else
                    s.canScrap = false;
                end
            end

            s.surface                   = props:Is("Surface") and tonumber(props:Val("Surface")) or 0;
            s.surfaceIsOffset           = props:Is("IsSurfaceOffset");
            s.yOffsetCursor             = 0; --render offset used in the movable cursor.
			
			s.isMoveable				= s.spriteName and props:Is("IsMoveAble") or false;
			
			s.facing 					= props:Is("Facing") and props:Val("Facing") or nil;
			s.isoType = props:Is("IsoType") and props:Val("IsoType") or "IsoObject";
			s.groupName					= props:Is("GroupName") and props:Val("GroupName") or nil;
			s.name 						= (s.groupName and (s.groupName .. " ") or "") .. (props:Is("CustomName") and props:Val("CustomName") or "Moveable object");

            if not s.isMoveable and s.canScrap and s.name == "Moveable object" then
                s.name                  = "Scrapable object";
            end

			if s.isMoveable then
                s.customItem            = props:Is("CustomItem") and props:Val("CustomItem") or nil;
				s.type 					= props:Is("MoveType") and props:Val("MoveType") or "Object";
				s.pickUpTool			= props:Is("PickUpTool") and props:Val("PickUpTool")~="None" and props:Val("PickUpTool") or nil;
				s.placeTool				= props:Is("PlaceTool") and props:Val("PlaceTool")~="None" and props:Val("PlaceTool") or nil;
                s.canBreak				= s.pickUpTool and props:Is("CanBreak"); --false; --s.pickUpTool and props:Is("CanBreak"); --can only break uppon tool use
				s.pickUpLevel			= props:Is("PickUpLevel") and tonumber(props:Val("PickUpLevel")) or 0; --0; --props:Is("PickUpLevel") and tonumber(props:Val("PickUpLevel")) or 0;
                s.rawWeight				= props:Is("PickUpWeight") and tonumber(props:Val("PickUpWeight")) or 50;
				s.weight				= s.rawWeight / 10; --0.1; -- s.rawWeight / 10; --props:Is("PickUpWeight") and tonumber(props:Val("PickUpWeight"))/10 or 1.0;
				s.isClosedState			= props:Is("IsClosedState") or false;
				s.Noffset				= props:Is("Noffset") and tonumber(props:Val("Noffset")) or 0;
				s.Soffset				= props:Is("Soffset") and tonumber(props:Val("Soffset")) or 0;
				s.Woffset				= props:Is("Woffset") and tonumber(props:Val("Woffset")) or 0;
				s.Eoffset				= props:Is("Eoffset") and tonumber(props:Val("Eoffset")) or 0;
				s.linkedOffset 			= props:Is("LinkedOffset") and tonumber(props:Val("LinkedOffset")) or 0;
				s.linkedLoc				= props:Is("LinkedLocIs") and props:Val("LinkedLocIs") or nil;
				s.wallOverlay			= props:Is(IsoFlagType.WallOverlay);
                s.ignoreSurfaceSnap     = props:Is("IgnoreSurfaceSnap");
                s.isGridExtensionTile   = props:Is("IsGridExtensionTile");
                s.allowDoorFrame        = props:Is("WallObjectAllowDoorframe");
				if s.wallOverlay then
					if s.type=="WallObject" then		-- is this sprite is walloverlay check if the assigned type is wallobject
						s.type = "WallOverlay";
					else
						s.isMoveable = false;			-- otherwise set this moveable to false;
						print("Error in moveables for sprite '"..tostring(s.spriteName).."', assigned type '"..tostring(s.type).."' does not match sprite overlay state '"..tostring(s.wallOverlay).."'")
					end
                end

                -- normally objects dont have a specific item bound to them so they default to the movable generic and read values like weight and name from the worldsprite props.
                -- however radios for example return a subclass of movable, their setWorldSprite function is overriden so the item values like name and weight only have to be set in items.txt
                -- the movables infopanels, when using the cursor, need to display info from the item instead of the spriteprops as well.
                if s.customItem then
                    local itemInstance = nil;
                    if not ISMoveableSpriteProps.itemInstances[s.customItem] then
                        itemInstance = instanceItem(s.customItem);
                        if itemInstance then
                            ISMoveableSpriteProps.itemInstances[s.customItem] = itemInstance;
                        end
                    else
                        itemInstance = ISMoveableSpriteProps.itemInstances[s.customItem];
                    end
                    if itemInstance then -- Override the following variables:
                        s.name = itemInstance:getName();
                        s.weight = itemInstance:getActualWeight();
                        s.rawWeight = s.weight*10;
                    end
                end

                s.isMultiSprite     = s.sprite:getSpriteGrid()~=nil;
                s.isForceSingleItem = props:Is("ForceSingleItem") or false;
			end
			
			if s.spriteName then
				local index				= lastIndexOf(s.spriteName, "_");
				if index then
					s.sheetName 		= s.spriteName:sub(1, index-1);
					s.spriteID 			= tonumber(s.spriteName:sub(index));
				end
				if s.isMoveable and tostring(s.sheetName)..tostring(s.spriteID) ~= s.spriteName then
					s.isMoveable = false;
				end				
			end
		end
	end
	return s;
end

function ISMoveableSpriteProps:getYOffsetCursor()
    return self.yOffsetCursor;
end

function ISMoveableSpriteProps:instanceItem(_spriteNameOverride)
	if self.isMoveable then
		local item;
        if self.customItem then
            item 	= instanceItem(self.customItem);
        else
            if self.isMultiSprite then
                item 	= instanceItem("Moveables.Moveable");
            else
                item 	= instanceItem("Moveables."..self.spriteName);
            end
            --item 	= instanceItem("Moveables."..self.spriteName); --instanceItem("Moveables.Moveable");
        end
        local spriteName = _spriteNameOverride or self.spriteName;
        if self.type == "Window" then                                   -- Some corrections
            if not self.spriteProps:Is("SmashedTileOffset") or self.spriteProps:Val("SmashedTileOffset") == 0 then
                return nil; -- cant instance broken windows
            end
        elseif self.type == "WindowObject" and self.isClosedState then
            spriteName = self.sheetName .. tostring(self.spriteID+4);
        end
		if item and instanceof(item, "Moveable") and item:ReadFromWorldSprite(spriteName) then
			item:setActualWeight(self.weight);
			return item;
		end
		if item and not instanceof(item, "Moveable") then
			return item; -- Base.Sheet
		end
	end
end

function ISMoveableSpriteProps:getFaceDirectionFromSpriteName( _face )
    local face;
    if _face and self.isMoveable and self:hasFaces() then
        for k,v in pairs(self:getFaces()) do
            if v==_face then
                return k;
            end
        end
    end
    return face;
end

function ISMoveableSpriteProps:hasFaces()
    local hasFaces = false;
    if self.isMoveable and self.facing ~= nil then
        local ownFace = self.facing;
        if ownFace~="N" and self.Noffset~=0 then hasFaces = true; end
        if ownFace~="W" and self.Woffset~=0 then hasFaces = true; end
        if ownFace~="S" and self.Soffset~=0 then hasFaces = true; end
        if ownFace~="E" and self.Eoffset~=0 then hasFaces = true; end
    end
    return hasFaces;
end

function ISMoveableSpriteProps:getFaces()
	local d = {};
	if self.isMoveable and self.facing ~= nil then
		local ownFace = self.facing;
		d[ownFace] = self.spriteName;
		if ownFace~="N" and self.Noffset~=0 then d.N = self.sheetName .. tostring( self.spriteID + self.Noffset ); end
		if ownFace~="W" and self.Woffset~=0 then d.W = self.sheetName .. tostring( self.spriteID + self.Woffset ); end
		if ownFace~="S" and self.Soffset~=0 then d.S = self.sheetName .. tostring( self.spriteID + self.Soffset ); end
		if ownFace~="E" and self.Eoffset~=0 then d.E = self.sheetName .. tostring( self.spriteID + self.Eoffset ); end
	end
	return d;
end

-- always returns four indexes for N W S E, if a face is missing will try to replace with face from same axis (NS and WE) otherwise replace with own face. (used for rotation business)
function ISMoveableSpriteProps:getIndexedFaces()
    local d = {};
    if self.isMoveable and self:hasFaces() then
        local ownFace = self.facing;
        local faces = self:getFaces();
        table.insert(d, faces.N or faces.S or ownFace ); -- 1
        table.insert(d, faces.W or faces.E or ownFace ); -- 2
        table.insert(d, faces.S or faces.N or ownFace ); -- 3
        table.insert(d, faces.E or faces.W or ownFace ); -- 4
    end
    return d;
end

function ISMoveableSpriteProps:getFaceIndex()
    for i,sprite in ipairs(self:getIndexedFaces()) do
        if sprite == self.spriteName then
            return i;
        end
    end
    return -1;
end

function ISMoveableSpriteProps:getObjectHealth()
    local matHealth = ISMoveableDefinitions:getInstance().getHealthDefinition( self.material ) or 1.0;
    return self.rawWeight * matHealth;
end

function ISMoveableSpriteProps:getBreakChance( _player )
	if ISMoveableDefinitions.cheat then return 0; end
	if _player and self.isMoveable and self.canBreak and self.pickUpTool then
        local toolDef = ISMoveableDefinitions:getInstance().getToolDefinition( self.pickUpTool ); --ISMoveableSpriteProps.toolDefinitions[self.pickUpTool];
        if toolDef then
            local perkLevel = _player:getPerkLevel(toolDef.perk);
            if self.type == "Window" then
                return 5+((10-perkLevel)*5);
			end
			if not toolDef.perk then
				return 25;
			end
            return (10-perkLevel)*7.5;
        else
            print("Missing tool definition for: "..tostring(self.pickUpTool));
        end
	end
	return 0;
end

-- "Great Cliff which instruments do you want us to break?!?"
function ISMoveableSpriteProps:doBreakTest( _player )
	if self.isMoveable then
		local breakChance = self:getBreakChance( _player );
		if breakChance == 0 then 
			return false; 
		else
			local randomNum = ZombRand(1,101);
			if randomNum <= breakChance then
				return true;
			end
		end
	end
	return false;
end

function ISMoveableSpriteProps:playBreakSound( _character, _object )
    if _character and _object and self.isMoveable and self.canBreak and self.material then
        if instanceof( _object, "IsoThumpable" ) then
            _character:playSound( _object:getBreakSound() )
        else
            _character:playSound( "BreakObject" )
        end
    end
end

function ISMoveableSpriteProps:addBreakDebris( _square )
	if _square and self.isMoveable and self.canBreak and self.material then
        local materialDefinitions = ISMoveableDefinitions:getInstance().getMaterialDefinition( self.material );
        if materialDefinitions then
            for i,v in ipairs(materialDefinitions) do
                self:addInventoryItemToSquare( _square, v.returnItem, v.maxAmount, v.chancePerRoll );
            end
        end
	end
end

function ISMoveableSpriteProps:addInventoryItemToSquare( _square, _item, _max, _chance )
    if _square and _item and _max and _chance then
        local amount = ZombRand(0,_max+1);
        if amount > 0 then
            for i = 1, amount do
                local randNum = ZombRand(0,101);
                if randNum < _chance then
                    local item 	= instanceItem( _item );
                    if item then
                        _square:AddWorldInventoryItem(item, 0.5, 0.5, 0);
                    end
                end
            end
        end
    end
end

--ISMoveableSpriteProps.toolDefinitions[_name] = { items = _items, perk = _perk, baseActionTime = _baseActionTime, sound = _sound, isWav = _isWav };
-- if pickup or place requires a tool then player gets 5% per carpentry (or customPerk) level speedbonus.
function ISMoveableSpriteProps:getActionTime( _player, _mode )
    if self.isMoveable and _player  and _mode then
        local tool = (_mode=="pickup" and self.pickUpTool) or (_mode=="place" and self.placeTool);
        local toolDef = ISMoveableDefinitions:getInstance().getToolDefinition( tool ); --ISMoveableSpriteProps.toolDefinitions[tool];
        if toolDef then
            local perkLevel = _player:getPerkLevel(toolDef.perk);
            local actionTime = toolDef.baseActionTime + (self.rawWeight*2); --FIXME keep *2 mod on rawweight?
            actionTime = actionTime - (actionTime*(perkLevel*0.05));
            if self.type == "Window" then actionTime = actionTime * 3; end --increased time for windows
            --print("time for this action = "..tostring(actionTime));
            return actionTime;
        end
    end
    --print("default action time");
    return 100;
end

function ISMoveableSpriteProps:hasRequiredSkill( _player, _mode )
	if ISMoveableDefinitions.cheat then return true; end
    if _mode == "scrap" then
        if not _player then return false end
        if not self.canScrap then return false end
        if not self.scrapUseSkill then return false end
        local scrapDef = ISMoveableDefinitions:getInstance().getScrapDefinition(self.material)
        if scrapDef and scrapDef.perk then
            local perkLevel = _player:getPerkLevel(scrapDef.perk)
            return true, scrapDef.perkName, scrapDef.perk
        end
        return false
    end
    if _player and self.isMoveable and _mode then
        local tool = (_mode=="pickup" and self.pickUpTool) or (_mode=="place" and self.placeTool);
        local toolDef = ISMoveableDefinitions:getInstance().getToolDefinition( tool ); -- ISMoveableSpriteProps.toolDefinitions[self.pickUpTool];
        if toolDef then
            local perkLevel = _player:getPerkLevel(toolDef.perk);
            if self.pickUpLevel > 0 then
                return (perkLevel>=self.pickUpLevel), toolDef.perkName, toolDef.perk;
            end
            return true, toolDef.perkName;
        end
    end
    return true;
end

function ISMoveableSpriteProps:hasTool( _player, _mode )
	if ISMoveableDefinitions.cheat then return true; end
    local tool = (_mode == "pickup" and self.pickUpTool) or (_mode == "place" and self.placeTool);
    if tool and _player then
        local inventory = _player:getInventory();
        local toolDef =  ISMoveableDefinitions:getInstance().getToolDefinition( tool ); -- ISMoveableSpriteProps.toolDefinitions[tool];
        if toolDef then
            for _,v in ipairs(toolDef.items) do
                local item = inventory:getFirstTypeRecurse(v);
                if item then
                    return item;
                end
            end
        end
    end
    return nil
end

function ISMoveableSpriteProps.addLineToInfoTable( _table, _lineA, _rA, _gA, _bA, _lineB, _rB, _gB, _bB)
    local line = {};
    table.insert(line, { txt = _lineA, r = _rA, g = _gA, b = _bA} );
    if _lineB then
        table.insert(line, { txt = _lineB, r = _rB, g = _gB, b = _bB} );
    end
    table.insert( _table, line);
    return _table;
end

local function getColorValues( _bool )
    if _bool then
        return ISMoveableSpriteProps.ghc:getR()*255, ISMoveableSpriteProps.ghc:getG()*255, ISMoveableSpriteProps.ghc:getB()*255;
    end
    return ISMoveableSpriteProps.bhc:getR()*255, ISMoveableSpriteProps.bhc:getG()*255, ISMoveableSpriteProps.bhc:getB()*255;
end

local function resetInfoPanelFlags()
    InfoPanelFlags.debug = nil;
    InfoPanelFlags.name = nil;
    InfoPanelFlags.weight = nil;
    InfoPanelFlags.hasItems = nil;
    InfoPanelFlags.canRotate = nil;
    InfoPanelFlags.hasSkill = nil;
    InfoPanelFlags.nameSkill = nil;
    InfoPanelFlags.perk = nil
    InfoPanelFlags.levelSkill = nil
    InfoPanelFlags.tool = nil;
    InfoPanelFlags.hasTool = nil;
    InfoPanelFlags.toolString = {}; --contains tools, possibly multiple lines (note to self: only first line tool had prefix)
    InfoPanelFlags.tool2 = nil;
    InfoPanelFlags.hasTool2 = nil;
    InfoPanelFlags.tool2String = {};
    InfoPanelFlags.scrapChance = nil;
    InfoPanelFlags.breakChance = nil;
    InfoPanelFlags.hasCompost = nil;
    InfoPanelFlags.tooHeavy = nil;
    InfoPanelFlags.tooHot = nil;
    InfoPanelFlags.itemsOnSurface = nil;
    InfoPanelFlags.hasWater = nil;
    InfoPanelFlags.notEmpty = nil;
    InfoPanelFlags.doorBarricaded = nil;
    InfoPanelFlags.doorInFrame = nil;
    InfoPanelFlags.floorAtTopOfStairs = nil;
    InfoPanelFlags.windowOpen = nil;
    InfoPanelFlags.windowBarricaded = nil;
    InfoPanelFlags.windowInFrame = nil;
    InfoPanelFlags.needStandingInside = nil;
    InfoPanelFlags.mustPlaceRoomRoof = nil;
    InfoPanelFlags.isOperational = nil; -- for stuff that cant be moved due it being operated (like bbq)
    InfoPanelFlags.removePropane = nil;
end

function ISMoveableSpriteProps:getInfoPanelDescription( _square, _object, _player, _mode )
    local infoTable = {};
    local bR,bG,bB = ISMoveableSpriteProps.bhc:getR()*255, ISMoveableSpriteProps.bhc:getG()*255, ISMoveableSpriteProps.bhc:getB()*255
    local gR,gG,gB = ISMoveableSpriteProps.ghc:getR()*255, ISMoveableSpriteProps.ghc:getG()*255, ISMoveableSpriteProps.ghc:getB()*255
    if _mode == "scrap" and not self.canScrap then
        return ISMoveableSpriteProps.addLineToInfoTable( infoTable, getText("IGUI_NoCanScrap"), bR,bG,bB );
    elseif _mode ~= "scrap" and not self.isMoveable then
        return ISMoveableSpriteProps.addLineToInfoTable( infoTable, getText("IGUI_NoMovable"), bR,bG,bB );
    else
        -- debug print
        if ISMoveableSpriteProps.debug then
            infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "DEBUG", 128, 128, 128 );
            for i=0, self.spriteProps:getPropertyNames():size()-1 do
                local name = self.spriteProps:getPropertyNames():get(i);
                infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, name, 255, 255, 255, tostring(self.spriteProps:Val(name)), gR,gG,gB );
            end
            infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "END DEBUG", 128, 128, 128 );
        end

        resetInfoPanelFlags();
        self:getInfoPanelFlagsGeneral( _square, _object, _player, _mode );
        if self.isMoveable and self.isMultiSprite then
            local sgrid = self:getSpriteGridInfo(_square, _mode == "pickup" or _mode == "scrap");
            if sgrid then
                for _,gridMember in ipairs(sgrid) do
                    self:getInfoPanelFlagsPerTile( gridMember.square, not gridMember.sprInstance and gridMember.object or nil, _player, _mode );
                end
            end
        else
            self:getInfoPanelFlagsPerTile( _square, _object, _player, _mode );
        end

        --##########################################
        if InfoPanelFlags.name then infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, getText("IGUI_Name")..":", 255, 255, 255, Translator.getMoveableDisplayName(InfoPanelFlags.name), 255, 255, 255 ); end
        if InfoPanelFlags.weight then infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, getText("Tooltip_item_Weight")..":", 255, 255, 255, tostring(InfoPanelFlags.weight), 255, 255, 255 ); end
        if InfoPanelFlags.nameSkill then
            local skillText = InfoPanelFlags.nameSkill
            if InfoPanelFlags.levelSkill ~= nil and InfoPanelFlags.levelSkill > 0 then
                skillText = skillText .. " " .. _player:getPerkLevel(InfoPanelFlags.perk) .. "/" .. InfoPanelFlags.levelSkill
            end
            infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, getText("IGUI_Skill")..":", 255, 255, 255, skillText, getColorValues(InfoPanelFlags.hasSkill) );
        end
        if #InfoPanelFlags.toolString > 0 then
            local first = true;
            for _,s in ipairs(InfoPanelFlags.toolString) do
                if first then
                    infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, getText("IGUI_Tool")..":", 255, 255, 255, s, getColorValues(InfoPanelFlags.hasTool) );
                else
                    infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "", 255, 255, 255, s, getColorValues(InfoPanelFlags.hasTool) );
                end
                first = false;
            end
        else
            infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, getText("IGUI_Tool")..":", 255, 255, 255, getText("IGUI_None"), getColorValues(true) );
        end
        if #InfoPanelFlags.tool2String > 0 then
            local first = true;
            for _,s in ipairs(InfoPanelFlags.tool2String) do
                if first then
                    infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, getText("IGUI_Tool")..":", 255, 255, 255, s, getColorValues(InfoPanelFlags.hasTool2) );
                else
                    infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "", 255, 255, 255, s, getColorValues(InfoPanelFlags.hasTool2) );
                end
                first = false;
            end
        end
        if InfoPanelFlags.scrapChance then
            local chance = ColorInfo.new(0, 0, 0, 1)
            getCore():getBadHighlitedColor():interp(getCore():getGoodHighlitedColor(), InfoPanelFlags.scrapChance/100, chance);
            infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, getText("Tooltip_Chance"), 255, 255, 255, tostring(InfoPanelFlags.scrapChance), chance:getR()*255,chance:getG()*255,chance:getB()*255 );
        end
        if InfoPanelFlags.breakChance then
            local chance = ColorInfo.new(0, 0, 0, 1)
            getCore():getGoodHighlitedColor():interp(getCore():getBadHighlitedColor(), InfoPanelFlags.breakChance/100, chance);
            infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, getText("IGUI_ChanceToBreak")..":", 255, 255, 255, tostring(InfoPanelFlags.breakChance), chance:getR()*255,chance:getG()*255,chance:getB()*255 );
        end
        if InfoPanelFlags.hasItems then
            infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "- "..getText("IGUI_ItemsInContainer"), bR,bG,bB );
        end
        if InfoPanelFlags.canRotate~=nil then
            if InfoPanelFlags.canRotate then infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, getText("IGUI_CanRotate"), gR,gG,gB );
            else infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, getText("IGUI_CanNotRotate"), bR,bG,bB ); end
        end
        if InfoPanelFlags.hasCompost then infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "- "..getText("IGUI_ComposterHasCompost"), bR,bG,bB ); end
        if InfoPanelFlags.tooHeavy then infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "- "..getText("IGUI_ToHeavy"), bR,bG,bB ); end
        if InfoPanelFlags.tooHot then infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "- "..getText("IGUI_TooHot"), bR,bG,bB ); end
        if InfoPanelFlags.itemsOnSurface then infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "- "..getText("IGUI_ItemsSurface"), bR,bG,bB ); end
        if InfoPanelFlags.hasWater then infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "- "..getText("IGUI_RainCollectorHasWater"), bR,bG,bB ); end
        if InfoPanelFlags.notEmpty then infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "- "..getText("IGUI_MovableHasMaterial"), bR,bG,bB ); end
        if InfoPanelFlags.doorBarricaded then infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "- "..getText("IGUI_Moveables_DoorBarricaded"), bR,bG,bB ); end
        if InfoPanelFlags.doorInFrame then infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "- "..getText("IGUI_Moveables_DoorInFrame"), bR,bG,bB ); end
        if InfoPanelFlags.floorAtTopOfStairs then infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "- "..getText("IGUI_Moveables_FloorAtTopOfStairs"), bR,bG,bB ); end
        if InfoPanelFlags.windowOpen then infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "- "..getText("IGUI_WindowOpen"), bR,bG,bB ); end
        if InfoPanelFlags.windowBarricaded then infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "- "..getText("IGUI_WindowBarricaded"), bR,bG,bB ); end
        if InfoPanelFlags.windowInFrame then infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "- "..getText("IGUI_Moveables_WindowInFrame"), bR,bG,bB ); end
        if InfoPanelFlags.needStandingInside then infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "- "..getText("IGUI_NeedToBeStandingInside"), bR,bG,bB ); end
        if InfoPanelFlags.mustPlaceRoomRoof then infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "- "..getText("IGUI_MustPlaceRoomRoof"), bR,bG,bB ); end
        if InfoPanelFlags.isOperational then infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "- "..getText("IGUI_IsOperational"), bR,bG,bB ); end
        if InfoPanelFlags.removePropane then infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "- "..getText("IGUI_RemovePropane"), bR,bG,bB ); end
        --##########################################
        return infoTable;
    end
end

function ISMoveableSpriteProps:getToolString( _itemTypes, _itemNames )
    local toolString = {}
    local addedNames = {}
    for k,v in ipairs(_itemTypes) do
        if not addedNames[_itemNames[v]] then
            addedNames[_itemNames[v]] = true
            table.insert(toolString, _itemNames[v])
        end
    end
    table.sort(toolString, function(a,b) return not string.sort(a, b) end)
    return toolString
end

function ISMoveableSpriteProps:getInfoPanelFlagsGeneral( _square, _object, _player, _mode )
    InfoPanelFlags.name = self.name;
    InfoPanelFlags.weight = self.weight and tostring(round(self.weight, 3)) or nil;
    if _mode and _mode == "rotate" then
        InfoPanelFlags.canRotate = self:canManuallyRotate();
    end
    local hasSkill, nameSkill, perk = self:hasRequiredSkill( _player, _mode );
    if nameSkill then
        InfoPanelFlags.hasSkill = hasSkill;
        InfoPanelFlags.nameSkill = nameSkill;
        InfoPanelFlags.perk = perk
        InfoPanelFlags.levelSkill = self.pickUpLevel
    end
    if _mode == "scrap" then
        InfoPanelFlags.weight = nil
        InfoPanelFlags.levelSkill = nil
        if self.scrapUseTool then
            local scrapDef = ISMoveableDefinitions:getInstance().getScrapDefinition( self.material )
            local toolTypes = scrapDef and scrapDef.tools or nil
            if toolTypes and #toolTypes > 0 then
                InfoPanelFlags.toolString = self:getToolString( toolTypes, scrapDef.toolNames )
                InfoPanelFlags.hasTool = self:hasScrapTool( _player, false )
            end
            toolTypes = scrapDef and scrapDef.tools2 or nil
            if toolTypes and #toolTypes > 0 then
                InfoPanelFlags.tool2String = self:getToolString( toolTypes, scrapDef.toolNames )
                InfoPanelFlags.hasTool2 = self:hasScrapTool( _player, true )
            end
        end
        local result,chance,perkName = self:canScrapObject( _player )
        if chance > 0 then
            InfoPanelFlags.scrapChance = chance
        end
    else
        InfoPanelFlags.tool = (_mode == "pickup" and self.pickUpTool) or (_mode == "place" and self.placeTool) or nil;
        InfoPanelFlags.hasTool = self:hasTool( _player, _mode );
        if InfoPanelFlags.tool then
            local toolDef = ISMoveableDefinitions:getInstance().getToolDefinition( InfoPanelFlags.tool )
            if toolDef then
                InfoPanelFlags.toolString = self:getToolString( toolDef.items, toolDef.itemNames )
            else
                table.insert(InfoPanelFlags.toolString, InfoPanelFlags.tool)
            end
        end
    end
    if _mode and _mode =="pickup" then
        local breakChance = self:getBreakChance( _player );
		if breakChance > 0 then
            InfoPanelFlags.breakChance = breakChance;
        end
    end
end

function ISMoveableSpriteProps:getInfoPanelFlagsPerTile( _square, _object, _player, _mode )
    InfoPanelFlags.hasItems = InfoPanelFlags.hasItems or (_object and instanceof(_object,"IsoObject") and not self:objectNoContainerOrEmpty( _object ));

    if _mode == "rotate" and self:canRotateDirection() then
        InfoPanelFlags.hasItems = false
    end

    if _mode == "pickup" and self.isoType == "IsoMannequin" then
        InfoPanelFlags.hasItems = false
    end

    if self.type=="WindowObject" then
        InfoPanelFlags.needStandingInside = _player:getSquare() and _player:getSquare():Is(IsoFlagType.exterior);
    end

    if _mode and _mode =="pickup" then
        print("pickup test", self:getTopTable(_square), _object)
        InfoPanelFlags.tooHeavy = InfoPanelFlags.tooHeavy or (not _player:getInventory():hasRoomFor(_player, self.weight));
        InfoPanelFlags.itemsOnSurface = InfoPanelFlags.itemsOnSurface or ((self.isTable and _square:Is("IsTableTop")) or (self.isTable and _object and _object ~= self:getTopTable(_square)));
        InfoPanelFlags.hasWater = InfoPanelFlags.hasWater or (self.isWaterCollector and _object and _object:hasWater());

        if CMetalDrumSystem.instance:isValidIsoObject(_object) and (_object:getModData().haveCharcoal or _object:getModData().haveLogs) then
            InfoPanelFlags.notEmpty = true;
        end
        
        if self.type == "Window" then
            InfoPanelFlags.windowOpen = InfoPanelFlags.windowOpen or (_object and instanceof(_object,"IsoWindow") and _object:IsOpen());
            InfoPanelFlags.windowBarricaded = InfoPanelFlags.windowBarricaded or (_object and instanceof(_object,"IsoWindow") and _object:isBarricaded());
        end

        if instanceof(_object, "IsoBarbecue") then
            InfoPanelFlags.isOperational = InfoPanelFlags.isOperational or _object:isLit();
            InfoPanelFlags.removePropane = InfoPanelFlags.removePropane or _object:hasPropaneTank();
        end

        if instanceof(_object, "IsoCompost") then
            InfoPanelFlags.hasCompost = InfoPanelFlags.hasCompost or (_object and _object:getCompost() > 0);
        end

        if instanceof(_object, "IsoFireplace") then
            InfoPanelFlags.tooHot = InfoPanelFlags.tooHot or (_object and (_object:isLit() or _object:isSmouldering()));
        end
    end

    if _mode and _mode =="place" then
        if self.type=="WindowObject" then
            InfoPanelFlags.mustPlaceRoomRoof = InfoPanelFlags.mustPlaceRoomRoof or (_square:Is(IsoFlagType.exterior));
        end
    end
    if _mode == "scrap" then
        InfoPanelFlags.itemsOnSurface = InfoPanelFlags.itemsOnSurface or ((self.isTable and _square:Is("IsTableTop")) or (self.isTable and _object and _object ~= self:getTopTable(_square)));
        InfoPanelFlags.hasWater = InfoPanelFlags.hasWater or (self.isWaterCollector and _object and _object:hasWater());
        InfoPanelFlags.doorBarricaded = InfoPanelFlags.doorBarricaded or (_object and instanceof(_object,"IsoDoor") and _object:isBarricaded());
        InfoPanelFlags.doorBarricaded = InfoPanelFlags.doorBarricaded or (_object and instanceof(_object,"IsoThumpable") and _object:isDoor() and _object:isBarricaded());
        InfoPanelFlags.doorInFrame = InfoPanelFlags.doorInFrame or (_object and instanceof(_object,"IsoThumpable") and _object:isDoorFrame() and _square:getDoor(_object:getNorth()));
        InfoPanelFlags.floorAtTopOfStairs = InfoPanelFlags.floorAtTopOfStairs or self:isFloorAtTopOfStairs(_object);
        InfoPanelFlags.windowInFrame = InfoPanelFlags.windowInFrame or (instanceof(_object,"IsoThumpable") and _object:isWindow() and _square:getWindow(_object:getNorth()))
	end
	
	if ISMoveableDefinitions.cheat then
		InfoPanelFlags.hasItems = false;
		InfoPanelFlags.tooHeavy = false;
	end
end

-- returns the moveable object if found on square, in case of wall overlays returns the wallobject and overlay spriteinstance.
function ISMoveableSpriteProps:findOnSquare( _square, _spriteName )
    if _square and _spriteName then
        for i = _square:getObjects():size(),1,-1 do
            local obj = _square:getObjects():get(i-1);
            if obj:getSprite() then
                local objSprite = obj:getSprite();
                local props = objSprite:getProperties();
                if objSprite:getName() and objSprite:getName() == _spriteName then
                    return obj;
                elseif props and (props:Is("WallNW") or props:Is("WallN") or props:Is("WallW")) then
                    local sprList = obj:getChildSprites();
                    if sprList and sprList:size() > 0 then
                        for j=sprList:size()-1, 0, -1 do
                            local sprite = sprList:get(j):getParentSprite();
                            if sprite and sprite:getName() and sprite:getName() == _spriteName then
                                return obj, sprList:get(j);
                            end
                        end
                    end
                end
            end
        end
    end
end

-- Finds the moveable defined by _spriteName in inventory
function ISMoveableSpriteProps:findInInventory( _character, _spriteName )
    if _character and _spriteName then
        local items 			= _character:getInventory():getItems();
        local items_size 		= items:size();
        for i=0,items_size-1, 1 do
            local item = items:get(i);
            if instanceof(item, "Moveable") and item:getWorldSprite() then
                if _character:getPrimaryHandItem() ~= item and _character:getSecondaryHandItem() ~= item then
                    if item:getWorldSprite() == _spriteName then
                        return item;
                    end
                end
            end
        end
    end
end

function ISMoveableSpriteProps:getObjectMoveProps( _obj )
    if _obj then
        local sprite = _obj:getSprite();
        if sprite then
            local moveProps = ISMoveableSpriteProps.new( sprite );
            if moveProps.isMoveable then
                return moveProps;
            end
        end
    end
end

function ISMoveableSpriteProps:isFreeTile( _square )
    if not _square or _square:Is("BlocksPlacement") or _square:Is(IsoFlagType.canBeCut) or _square:Is("tree") then
        return false;
    end
    return true;
end

function ISMoveableSpriteProps:objectNoContainerOrEmpty( _object )
    for i=1,_object:getContainerCount() do
        local con = _object:getContainerByIndex(i-1)
        if con and (con:getItems() and not con:getItems():isEmpty()) or not con:isExplored() then
            return false
        end
    end
    return true;
end

-- with verify bool it ONLY checks if the current spritegridcache is correct, otherwise it will update the spritegridcache table
-- returns a bool if spritegrid is correct
-- for pick up actions _getWorldObjects can be set to include worldobjects
function ISMoveableSpriteProps:getSpriteGridCache( _square, _verifyOnly, _getWorldObjects )
    if not _verifyOnly then SpriteGridCache = {}; end

    if self.isMoveable and self.isMultiSprite then
        local spriteGrid = self.sprite:getSpriteGrid();
        if not spriteGrid then return false; end

        local sX = _square:getX() - spriteGrid:getSpriteGridPosX(self.sprite);
        local sY = _square:getY() - spriteGrid:getSpriteGridPosY(self.sprite);
        local sZ = _square:getZ();

        local index = 1;
        for x=0,spriteGrid:getWidth()-1 do
            for y=0,spriteGrid:getHeight()-1 do
                local square = getCell():getGridSquare(sX+x, sY+y, sZ);
                local spriteForPos = spriteGrid:getSprite(x, y);

                if _verifyOnly and not SpriteGridCache[index] then
                    return false;
                end

                local obj, sprInstance  = false;
                if _getWorldObjects then
                    obj, sprInstance = self:findOnSquare( square, spriteForPos:getName() );
                    if not obj then
                        return false;
                    end
                end
                if not _verifyOnly then
                    table.insert(SpriteGridCache, { square = square, object = obj, sprInstance = sprInstance, sprite = spriteForPos, x = x, y = y } );
                else
                    if not (SpriteGridCache[index].square == square
                            and SpriteGridCache[index].object == obj
                            and SpriteGridCache[index].sprInstance == sprInstance
                            and SpriteGridCache[index].sprite == spriteForPos
                            and SpriteGridCache[index].x == x
                            and SpriteGridCache[index].y == y) then
                        return false;
                    end
                end

                --[[
                local foundObj = false;
                if _getWorldObjects then
                    for i = square:getObjects():size(),1,-1 do
                        local obj = square:getObjects():get(i-1);
                        if obj:getSprite() == spriteForPos then
                            foundObj = obj;
                            break;
                        end
                    end
                end

                if _getWorldObjects and not foundObj then
                    return false;
                else
                    if not _verifyOnly then
                        table.insert(SpriteGridCache, { square = square, object = foundObj, sprite = spriteForPos, x = x, y = y } );
                    else
                        if not SpriteGridCache[index].square == square and SpriteGridCache[index].object == foundObj and SpriteGridCache[index].sprite == spriteForPos and SpriteGridCache[index].x == x and SpriteGridCache[index].y == y then
                            return false;
                        end
                    end
                end
                --]]
                index = index + 1;
            end
        end
        return true;
    end
    return false;
end

-- returns SpriteGridCache, updates cache table only on change.
function ISMoveableSpriteProps:getSpriteGridInfo( _square, _getWorldObjects )
    if self:getSpriteGridCache( _square, true, _getWorldObjects ) or self:getSpriteGridCache( _square, false, _getWorldObjects ) then
        return SpriteGridCache;
    end
end

--in case of wall objects, _object is not passed (nil)
function ISMoveableSpriteProps:canPickUpMoveable( _character, _square, _object )
    --print("MultiSprite movable CanPickUp test")
	if ISMoveableDefinitions.cheat then
		self.yOffsetCursor = _object and _object:getRenderYOffset() or 0;
		return true;
	end
    if self.isMoveable and self.isMultiSprite then
        local sgrid = self:getSpriteGridInfo(_square, true);
        if not sgrid then return false; end
        --print("take 2")
        for _,gridMember in ipairs(sgrid) do
            if not self:canPickUpMoveableInternal( _character, gridMember.square, not gridMember.sprInstance and gridMember.object or nil, true ) then
                return false;
            end
        end
        --print("returning true")
        return true;
    else
        return self:canPickUpMoveableInternal( _character, _square, _object, false );
    end
end

function ISMoveableSpriteProps:canPickUpMoveableInternal( _character, _square, _object, _isMulti )
    local canPickUp = false;
    if self.isMoveable and instanceof(_square,"IsoGridSquare") then
        canPickUp = not _object and true or self:objectNoContainerOrEmpty( _object );
        if not _isMulti and canPickUp then
            canPickUp = _character:getInventory():hasRoomFor(_character, self.weight);
        end
        if canPickUp and self.isTable then
            canPickUp = not _square:Is("IsTableTop") and _object == self:getTopTable(_square);
        end
        self.yOffsetCursor = _object and _object:getRenderYOffset() or 0;

        if canPickUp and self.isWaterCollector then
            if _object and _object:hasWater() then
                canPickUp = false
            end
        end

        if canPickUp and CMetalDrumSystem.instance:isValidIsoObject(_object) and
                (_object:getModData().haveCharcoal or _object:getModData().haveLogs) then
            canPickUp = false
        end
        
        if canPickUp and self.type == "Window" then
            if _object and instanceof(_object,"IsoWindow") then
                canPickUp = _object:isDestroyed() or (not _object:IsOpen());        -- only allow pickup when destroyed or closed (destroyed will remove window no return item)
                if _object:isBarricaded() then
                    canPickUp = false
                end
            else
                canPickUp = false;
            end
        end

        if canPickUp and self.type == "WindowObject" then
            if _character:getSquare() and _character:getSquare():Is(IsoFlagType.exterior) then
                canPickUp = false;
            end
        end

        if self.isoType == "IsoMannequin" then
            canPickUp = true
        end

        if instanceof(_object, "IsoBarbecue") then
            canPickUp = not _object:isLit() and not _object:hasPropaneTank();
        end

        if instanceof(_object, "IsoFireplace") then
            canPickUp = not (_object:isLit() or _object:isSmouldering())
        end

        if canPickUp and _character and instanceof(_character,"IsoGameCharacter") then
            local hasSKill, _ = self:hasRequiredSkill( _character, "pickup" );
            local hasTool = not self.pickUpTool and true or self:hasTool( _character, "pickup" );
            canPickUp = hasSKill and hasTool;
        end
    end
    return canPickUp;
end

function ISMoveableSpriteProps:pickUpMoveableViaCursor( _character, _square, _origSpriteName, _moveCursor )
    self:pickUpMoveable(_character, _square, true);
    if _moveCursor then
        _moveCursor:clearCache()
    end
end

function ISMoveableSpriteProps:pickUpMoveable( _character, _square, _createItem, _forceAllow )
    if self.isMoveable and instanceof(_character,"IsoGameCharacter") and instanceof(_square,"IsoGridSquare") then
        local obj, sprInstance = self:findOnSquare( _square, self.spriteName );
        local items = {};
        if obj and (_forceAllow or ISMoveableDefinitions.cheat or self:canPickUpMoveable( _character, _square, not sprInstance and obj or nil )) then
            if self.isMultiSprite then
                local sgrid = self:getSpriteGridInfo(_square, true);
                if not sgrid then return false; end

                local createItem = _createItem and not self.isForceSingleItem;
                for _,gridMember in ipairs(sgrid) do
                    table.insert(items, self:pickUpMoveableInternal( _character, gridMember.square, gridMember.object, gridMember.sprInstance, gridMember.sprite:getName(), createItem, _forceAllow ));
                end

                if _createItem and self.isForceSingleItem then
                    local spriteGrid = self.sprite:getSpriteGrid();
                    if not spriteGrid then return false; end

                    local item 	= self:instanceItem(spriteGrid:getAnchorSprite():getName());
                    _character:getInventory():AddItem(item);
                end
            else
                --local obj, sprInstance = self:findOnSquare( _square, self.spriteName );
                self:pickUpMoveableInternal( _character, _square, obj, sprInstance, self.spriteName, _createItem, _forceAllow );
            end
            ISMoveableCursor.clearCacheForAllPlayers();
            return items;
        end
    end
end

function ISMoveableSpriteProps:pickUpMoveableInternal( _character, _square, _object, _sprInstance, _spriteName, _createItem, _rotating )
    --if _object and self:canPickUpMoveable( _character, _square, not _sprInstance and _object or nil ) then
    local objIsIsoWindow = self.type == "Window" and instanceof(_object,"IsoWindow");
    local item 	= self:instanceItem(_spriteName);

    if item or (objIsIsoWindow and _object:isDestroyed()) then      -- destroyed windows return nil for instanceItem()
        local windowGotSmashed = false;
        if not objIsIsoWindow or not _object:isDestroyed() then     -- when its a destroyed window skip this
            if not _rotating and self:doBreakTest( _character ) then
                if self.type ~= "Window" then
                    self:playBreakSound( _character, _object );
                    self:addBreakDebris( _square );
                elseif objIsIsoWindow then
                    if not _object:isDestroyed() then               -- in case of a window, when it breaks and isnt broken yet smash it, leaves no debris.
                        _object:smashWindow();
                        windowGotSmashed = true;
                    end
                end
            elseif item then
                if instanceof(_object, "IsoThumpable") then
                    item:getModData().name = _object:getName() or ""
                    item:getModData().health = _object:getHealth()
                    item:getModData().maxHealth = _object:getMaxHealth()
                    item:getModData().thumpSound = _object:getThumpSound()
                    item:getModData().color = _object:getCustomColor()
                    if _object:hasModData() then
                        item:getModData().modData = copyTable(_object:getModData())
                    end
                else
                    if _object:hasModData() and _object:getModData().movableData then
                        item:getModData().movableData = copyTable(_object:getModData().movableData)
                    end

                    if _object:hasModData() and _object:getModData().itemCondition then
                        item:setConditionMax(_object:getModData().itemCondition.max);
                        item:setCondition(_object:getModData().itemCondition.value);
                    end
                end
                if _createItem then
                    if self.isMultiSprite then
                        _square:AddWorldInventoryItem(item, ZombRandFloat(0.1,0.9), ZombRandFloat(0.1,0.9), 0);
                    else
                        _character:getInventory():AddItem(item);        -- add the item if it aint got broken
                    end
                end
            end
        end

        -- custom/modified light info (custom bulb, use battery etc) for the various lamps can by copied to movable item and retrieved uppon placing;
        if instanceof(_object,"IsoLightSwitch") and _sprInstance==nil then
            _object:setCustomSettingsToItem(item);
            --item:getLightSettings(obj);
        end

        if instanceof(_object, "IsoMannequin") then
            _object:setCustomSettingsToItem(item)
        end

        -- Remove stuff from the world
        if self.type == "WallOverlay" then
            -- A Mirror on the east or south edge of a square.
            if _object:getSprite() and _spriteName and (_object:getSprite():getName() == _spriteName) then
                triggerEvent("OnObjectAboutToBeRemoved", _object) -- Hack for RainCollectorBarrel, Trap, etc
                _square:transmitRemoveItemFromSquare(_object)
            elseif _sprInstance then
                local sprList = _object:getChildSprites();
                local sprIndex = sprList and sprList:indexOf(_sprInstance) or -1
                if sprIndex == -1 then
                else
                    _object:RemoveAttachedAnim(sprIndex)
                    if isClient() then _object:transmitUpdatedSpriteToServer() end
                end
            end
        elseif self.type == "FloorTile" then
            local floor = _square:getFloor();
            local moveableDefinitions = ISMoveableDefinitions:getInstance();
            if moveableDefinitions and moveableDefinitions.floorReplaceSprites then
                local repSprs = moveableDefinitions.floorReplaceSprites;
                local floor = _square:getFloor();
                local spr = getSprite( repSprs[ ZombRand(1,#repSprs) ] );
                if floor and spr then
                    floor:setSprite(spr);
                    if isClient() then floor:transmitUpdatedSpriteToServer(); end --:transmitCompleteItemToServer(); end
                end
            end
        elseif self.isoType == "IsoBrokenGlass" then
            -- add random damage to hands if no gloves
            if not _character:getClothingItem_Hands() and ZombRand(3) == 0 then
                local handPart = _character:getBodyDamage():getBodyPart(BodyPartType.FromIndex(ZombRand(BodyPartType.ToIndex(BodyPartType.Hand_L),BodyPartType.ToIndex(BodyPartType.Hand_R) + 1)))
                handPart:setScratched(true, true);
                -- possible glass in hands
                if ZombRand(5) == 0 then
                    handPart:setHaveGlass(true);
                end
            end
            triggerEvent("OnObjectAboutToBeRemoved", _object)
            _square:transmitRemoveItemFromSquare(_object)
        elseif self.type == "Window" then
            if objIsIsoWindow and not windowGotSmashed then
                if isClient() then _square:transmitRemoveItemFromSquare(_object) end
                _square:RemoveTileObject(_object);
            end
        elseif not _sprInstance then --Objects, Vegitation, WallObjects etc
            if self.isoType == "IsoRadio" or self.isoType == "IsoTelevision" then
                if instanceof(_object,"IsoWaveSignal") then
                    local deviceData = _object:getDeviceData();
                    if deviceData then
                        item:setDeviceData(deviceData);
                    else
                        print("Warning: device data missing?>?")
                    end
                end
            end
            if self.spriteProps and not self.spriteProps:Is(IsoFlagType.waterPiped) then
                --print("water check");
                if _object:hasModData() then
                    --print("water check mod data");
                    if _object:getModData().waterAmount then
                        item:getModData().waterAmount = _object:getModData().waterAmount;
                        item:getModData().taintedWater = _object:isTaintedWater();
                    end
                else
                    --print("water check no mod");
                    local waterAmount = tonumber(_object:getWaterAmount());
                    if waterAmount then
                        item:getModData().waterAmount = waterAmount;
                        item:getModData().taintedWater = _object:isTaintedWater();
                end
                end
                --print("ITEM WATER AMOUNT = "..tostring(item:getModData().waterAmount));
            end
            triggerEvent("OnObjectAboutToBeRemoved", _object) -- Hack for RainCollectorBarrel, Trap, etc
            _square:transmitRemoveItemFromSquare(_object)
        end
        _square:RecalcProperties();
        _square:RecalcAllWithNeighbours(true);

        --ISMoveableCursor.clearCacheForAllPlayers();

        triggerEvent("OnContainerUpdate")

        IsoGenerator.updateGenerator(_square)
        return item;
    end
    --end
end

function ISMoveableSpriteProps:getTopTable( _square )
    if not instanceof(_square, "IsoGridSquare") then return nil end
    for i = _square:getObjects():size(),1,-1 do
        local obj = _square:getObjects():get(i-1)
        local sprite = obj:getSprite()
        if sprite and sprite:getProperties() then
            local props = sprite:getProperties()
--            print("check item ", obj:getSprite():getName(), props:Is("IsTable"), props:Is("Surface"), tonumber(props:Val("Surface")))
            if props:Is("IsTable") and props:Is("Surface") and tonumber(props:Val("Surface")) then
--                print("return ", obj)
                return obj
            end
        end
    end
    return nil
end

function ISMoveableSpriteProps:getTotalTableHeight( _square )
    local obj = self:getTopTable(_square)
    if not obj then return 0 end
    local props = obj:getSprite():getProperties()
    return obj:getRenderYOffset() + tonumber(props:Val("Surface"))
end

-- Finds the moveable defined by _spriteName in inventory
function ISMoveableSpriteProps:findInInventoryMultiSprite( _character, _spriteName )
    if _character and _spriteName then
        local items 			= _character:getInventory():getItems();
        local items_size 		= items:size();
        for i=0,items_size-1, 1 do
            local item = items:get(i);
            if instanceof(item, "Moveable") and self.customItem and (item:getFullType() == self.customItem) and (item:getName() == self.name) then
                return item, _character:getInventory();
            end
            if instanceof(item, "Moveable") and item:getCustomNameFull() then
                if _character:getPrimaryHandItem() ~= item and _character:getSecondaryHandItem() ~= item then
                    if item:getCustomNameFull() == _spriteName then
                        return item, _character:getInventory();
                    end
                end
            end
        end

        local radius = ISMoveableSpriteProps.multiSpriteFloorRadius;
        local square = _character:getSquare();
        if square then
            --print("try find square ".._spriteName);
            local sx,sy,sz = square:getX(), square:getY(), square:getZ();
            for x = sx-radius,sx+radius do
                for y = sy-radius,sy+radius do
                    --print(" test "..tostring(x)..":"..tostring(y)..":"..tostring(sz));
                    local sq = getCell():getGridSquare(x,y,sz);
                    if sq and sq:getWorldObjects() then
                        local items 			= sq:getWorldObjects();
                        local items_size 		= items:size();
                        for i=0,items_size-1, 1 do
                            if instanceof(items:get(i), "IsoWorldInventoryObject") then
                                local item = items:get(i):getItem();
                                if instanceof(item, "Moveable") and self.customItem and (item:getFullType() == self.customItem) and (item:getName() == self.name) then
                                    return item, "floor";
                                end
                                if item and instanceof(item, "Moveable") and item:getCustomNameFull() then
                                    if item:getCustomNameFull() == _spriteName then
                                        return item, "floor";
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end

    end
end

function ISMoveableSpriteProps:canPlaceMoveable( _character, _square, _item )
    if self.isMoveable and self.isMultiSprite then
        local spriteGrid = self.sprite:getSpriteGrid();
        if not spriteGrid then return false; end

        local sX = _square:getX() - spriteGrid:getSpriteGridPosX(self.sprite);
        local sY = _square:getY() - spriteGrid:getSpriteGridPosY(self.sprite);
        local sZ = _square:getZ();

        local square = getCell():getGridSquare(sX, sY, sZ);

        local sgrid = self:getSpriteGridInfo(square, false);
        if not sgrid then return false; end

        if not self.isForceSingleItem then
            local max = spriteGrid:getSpriteCount();
            for i,gridMember in ipairs(sgrid) do
                local item, container = self:findInInventoryMultiSprite( _character, self.name .. " (" .. i .. "/" .. max .. ")" ); -- gridMember.sprite:getName() );
                --[[
                if not item or not item:getWorldSprite() then
                    return false;
                end
                local elementProps = ISMoveableSpriteProps.new( item:getWorldSprite() );
                print("is extension tile: "..tostring(elementProps.isGridExtensionTile));
                if not elementProps or not self:canPlaceMoveableInternal( _character, gridMember.square, item, elementProps.isGridExtensionTile ) then
                    return false;
                end
                --]]
                if not item or not self:canPlaceMoveableInternal( _character, gridMember.square, item ) then
                    return false;
                end
                if container and container=="floor" then
                    local radius = ISMoveableSpriteProps.multiSpriteFloorRadius-1;
                    if radius < 1 then radius = 1 end;
                    if _square:getX() < _character:getX()-(radius+1) or _square:getX() > _character:getX()+radius then
                        return false;
                    end
                    if _square:getY() < _character:getY()-(radius+1) or _square:getY() > _character:getY()+radius then
                        return false;
                    end
                end
            end
        else
            --if self.sprite ~= spriteGrid:getAnchorSprite() then
                --return false;
            --end
            local item = self:findInInventoryMultiSprite( _character, self.name .. " (1/1)" );--self:findInInventory( _character, spriteGrid:getAnchorSprite():getName() );
            for i,gridMember in ipairs(sgrid) do
                if not item or not self:canPlaceMoveableInternal( _character, gridMember.square, item ) then
                    return false;
                end
            end
        end

        -- Check for wall-like things between squares
        if self:isWallBetweenParts(spriteGrid, sX, sY, sZ) then
            return false
        end

        return true;
    else
        return self:canPlaceMoveableInternal( _character, _square, _item );
    end
end

function ISMoveableSpriteProps:isWallBetweenParts( _spriteGrid, _x, _y, _z )
    for x = 1,_spriteGrid:getWidth() do
        for y = 1,_spriteGrid:getHeight() do
            local square = getCell():getGridSquare(_x + x - 1, _y + y - 1, _z)
            if square and _spriteGrid:getSprite(x - 1, y - 1) then
                if x < _spriteGrid:getWidth() then
                    local sqE = getCell():getGridSquare(square:getX() + 1, square:getY(), square:getZ())
                    if sqE and square:isSomethingTo(sqE) then
                        return true
                    end
                end
                if y < _spriteGrid:getHeight() then
                    local sqS = getCell():getGridSquare(square:getX(), square:getY() + 1, square:getZ())
                    if sqS and square:isSomethingTo(sqS) then
                        return true
                    end
                end
            end
        end
    end
    return false
end

function ISMoveableSpriteProps:isSquareAtTopOfStairs( _square )
    if not _square then return false end
    local x,y,z = _square:getX(),_square:getY(),_square:getZ()
    if z == 0 then return false end

    local belowEast = getCell():getGridSquare(x+1, y, z-1)
    if belowEast and belowEast:Has(IsoObjectType.stairsTW) then return true end

    local belowSouth = getCell():getGridSquare(x, y+1, z-1)
    if belowSouth and belowSouth:Has(IsoObjectType.stairsTN) then return true end

    return false
end

function ISMoveableSpriteProps:canPlaceMoveableInternal( _character, _square, _item, _forceTypeObject )
    local canPlace = false;
    if _square and _square:isVehicleIntersecting() then return false end
    if self.isMoveable then
        local hasTileFloor = _square and _square:getFloor();
        if not hasTileFloor and self.type ~= "Window" then
            return false;
        end

        if self.type == "Object" or _forceTypeObject then
            if self.isTableTop then
                --canPlace = _square:Is("IsTable") and not _square:Is("IsTableTop");			-- if the object is a tabletop then see if sq has table and no tabletop obj
                local currentSurface = self:getTotalTableHeight(_square);
                if _square:Is("IsTable") then
                    canPlace = not _square:Is("IsTableTop") and (currentSurface <= 64);
                else
                    canPlace = self.blocksPlacement and self:isFreeTile( _square ); -- if not a table present and item is tabletop, check if item has blocksplacement flag wich allows placing on floor (ommits stuff like sinks being placed on floors)
                end
                -- incase of table set the yoffset for cursor rendering
                if self.surface and self.surfaceIsOffset then
                    currentSurface = currentSurface - self.surface;
                end
                self.yOffsetCursor = currentSurface;
            elseif self:isFreeTile( _square ) then
                canPlace = true;
                if _square:Is("IsHigh") then
                    canPlace = self.isLow;							-- if theres a high object (on wall or so) then check if item is low
                end
                if _square:Is("IsLow") then
                    canPlace = self.isHigh;						-- if theres a low object then check if item is high (this probably is never or rarely a reality, added just in case)
                end
            elseif self.isStackable and self.isTable and self.surface and _square:Is("IsTable") and not _square:Is("IsTableTop") and not _square:Is("IsHigh") then
                local totalTableHeight = self:getTotalTableHeight(_square)
                if totalTableHeight + self.surface <= 96 then
                    canPlace = true
                end
                self.yOffsetCursor = totalTableHeight
            end
            if self:isSquareAtTopOfStairs(_square) then
                canPlace = false
            end
        elseif self.type == "WallObject" then
            --print("testing wall object");
            if self.allowDoorFrame then --self.name=="Exit Sign" then
                canPlace = self.facing and self:getWallForFacing( _square, self.facing, "WallAndDoor" ); -- self:hasFaces() and self:getWallForFacing( _square, self.facing, "WallAndDoor" );
            else
                --print("has faces: "..tostring(self.facing))
                --print("getwallforface "..tostring(self:getWallForFacing( _square, self.facing ) or "nil"))
                canPlace = self.facing and self:getWallForFacing( _square, self.facing );
            end
            if canPlace then
                --print("canplace")
                for i = 0, _square:getObjects():size()-1 do
                    local obj = _square:getObjects():get(i);
                    local sprite = obj:getSprite();
                    if sprite and sprite:getProperties() then
                        local props = sprite:getProperties();
                        if props then
                            if props:Is("MoveType") and props:Val("MoveType") == "WallObject" then
                                if props:Is("Facing") and props:Val("Facing") == self.facing then
                                    if (not props:Is("IsHigh")) and (not props:Is("IsLow")) then
                                        canPlace = false;
                                    elseif props:Is("IsHigh") and self.isHigh then
                                        canPlace = false;
                                    elseif props:Is("IsLow") and self.isLow then
                                        canPlace = false;
                                    end
                                end
                            elseif props:Is("BlocksPlacement") then
                                if (not props:Is("IsHigh")) and (not props:Is("IsLow")) then
                                    canPlace = false;
                                elseif props:Is("IsHigh") and self.isHigh then
                                    canPlace = false;
                                elseif props:Is("IsLow") and self.isLow then
                                    canPlace = false;
                                end
                            end
                        end
                        -- Some moveable objects such as mirrors are child sprites of a wall.
                        local sprList = obj:getChildSprites()
                        if canPlace and sprList then
                            for j=1,sprList:size() do
                                sprite = sprList:get(j-1):getParentSprite()
                                props = sprite:getProperties()
                                if props then
                                    if props:Is("MoveType") and props:Val("MoveType") == "WallObject" then
                                        if props:Is("Facing") and props:Val("Facing") == self.facing then
                                            if (not props:Is("IsHigh")) and (not props:Is("IsLow")) then
                                                canPlace = false;
                                            elseif props:Is("IsHigh") and self.isHigh then
                                                canPlace = false;
                                            elseif props:Is("IsLow") and self.isLow then
                                                canPlace = false;
                                            end
                                        end
                                    elseif props:Is("BlocksPlacement") then
                                        if (not props:Is("IsHigh")) and (not props:Is("IsLow")) then
                                            canPlace = false;
                                        elseif props:Is("IsHigh") and self.isHigh then
                                            canPlace = false;
                                        elseif props:Is("IsLow") and self.isLow then
                                            canPlace = false;
                                        end
                                    end
                                end
                                if not canPlace then
                                    break
                                end
                            end
                        end
                        if not canPlace then
                            --print("CANNOT PLACE");
                            break;
                        end
                    end
                end
            end
        elseif self.type == "WallOverlay" then
            local wall = self:getWallForFacing( _square, self.facing );
            if wall then
                canPlace =  not self:getMoveableOverlayFromObject( wall );                          --FIXME - check for wall objects? or leave like this (currently allows for weird placement however in certain situations allows you to do cool stuff
            end                                                                                     --FIXME - like placing mirrors above counters then adding a wall counter creates awesome bar look :D)
        elseif self.type == "FloorTile" then
            if hasTileFloor and hasTileFloor:getSprite() and hasTileFloor:getSprite():getName() then
                local fspr = hasTileFloor:getSprite():getName();
                local moveableDefinitions = ISMoveableDefinitions:getInstance();
                if moveableDefinitions.isFloorReplaceSprite( fspr ) then
                    canPlace = true;
                end
            end
        elseif self.type == "FloorRug" then
            canPlace = true;
            if not _square or _square:Is(IsoFlagType.canBeCut) or _square:Is("tree") then
                canPlace = false;
            end
            if canPlace then
                for i = 0, _square:getObjects():size()-1 do
                    local obj = _square:getObjects():get(i);
                    local sprite = obj:getSprite();
                    if sprite and sprite:getProperties() then
                        local props = sprite:getProperties();
                        if props then
                            if props:Is("MoveType") and props:Val("MoveType") == "FloorRug" then
                                canPlace = false;
                                break;
                            end
                        end
                    end
                end
            end
        elseif self.type == "Vegitation" then
            local sqTexName 	= _square:getFloor() and _square:getFloor():getSprite() and _square:getFloor():getSprite():getName() or false;
            if sqTexName and ( string.sub(sqTexName, 1, string.len("blends_natural")) == "blends_natural" ) then
                canPlace = self:isFreeTile( _square );
            end
        elseif self.type == "WindowObject" then
            canPlace = self:hasFaces() and self:getWallForFacing( _square, self.facing, "WindowFrame" );
            if canPlace then
                if _square:Is(IsoFlagType.exterior) or (_character:getSquare() and _character:getSquare():Is(IsoFlagType.exterior)) then
                    canPlace = false;
                end
            end
            if canPlace then
                for i = 0, _square:getObjects():size()-1 do
                    local obj = _square:getObjects():get(i);
                    local sprite = obj:getSprite();
                    if sprite and sprite:getProperties() then
                        local props = sprite:getProperties();
                        if props then
                            if props:Is("MoveType") and props:Val("MoveType") == "WindowObject" then
                                if props:Is("Facing") and props:Val("Facing") == self.facing then
                                    canPlace = false;
                                    break;
                                end
                            end
                        end
                    end
                    -- Forbid placing furniture-curtain on sheet-curtain.
                    if instanceof(obj, "IsoCurtain") then
                        if self.facing == "S" and obj:getType() == IsoObjectType.curtainN then
                            canPlace = false
                            break
                        end
                        if self.facing == "N" and obj:getType() == IsoObjectType.curtainS then
                            canPlace = false
                            break
                        end
                        if self.facing == "E" and obj:getType() == IsoObjectType.curtainW then
                            canPlace = false
                            break
                        end
                        if self.facing == "W" and obj:getType() == IsoObjectType.curtainE then
                            canPlace = false
                            break
                        end
                    end
                end
            end
        elseif self.type == "Window" then
            local isNorth = (self:hasFaces() and self.facing == "N" or self.facing == "S") or false;
            local wallFrame = self:getWallForFacing( _square, isNorth and "S" or "E", "WindowFrame" );
            if wallFrame then
                local window    = _square:getWindow(isNorth);
                canPlace = not window;
--[[
                if window and window:getSprite() and window:getSprite():getName() then
                    if ((not isNorth) and window:getSprite():getName() == "walls_special_01_8") or (isNorth and window:getSprite():getName() == "walls_special_01_9") then
                        canPlace = true;
                    end
                end
--]]
            end
        end

        if canPlace and _character and instanceof(_character, "IsoPlayer") then
			if not ISMoveableDefinitions.cheat then
				local hasSKill, _ = self:hasRequiredSkill( _character, "place" );
				local hasTool = not self.placeTool and true or self:hasTool( _character, "place" );
				canPlace = hasSKill and hasTool;
			end
        end
    end
    return canPlace;
end

function ISMoveableSpriteProps:placeMoveableViaCursor( _character, _square, _origSpriteName, _moveCursor )
    self:placeMoveable(_character, _square, _origSpriteName );
    if _moveCursor then
        _moveCursor:clearCache()
    end
end

function ISMoveableSpriteProps:placeMoveable( _character, _square, _origSpriteName )
    if self.isMoveable and instanceof(_character,"IsoGameCharacter") and instanceof(_square,"IsoGridSquare") then
        if self.isMultiSprite then
            local spriteGrid = self.sprite:getSpriteGrid();
            if not spriteGrid then return false; end

            local sgrid = self:getSpriteGridInfo(_square, false);
            if not sgrid then return false; end

            if not self.isForceSingleItem then
                local max = spriteGrid:getSpriteCount();
                local items = {};
                for i,gridMember in ipairs(sgrid) do
                    local item, container = self:findInInventoryMultiSprite( _character, self.name .. " (" .. i .. "/" .. max .. ")" );
                    --[[
                    if not item or not item:getWorldSprite() then
                        return false;
                    end
                    local elementProps = ISMoveableSpriteProps.new( item:getWorldSprite() );
                    if not elementProps or not self:canPlaceMoveableInternal( _character, gridMember.square, item, elementProps.isGridExtensionTile ) then
                        return false;
                    end
                    --]]
                    --local item = self:findInInventory( _character, gridMember.sprite:getName() );
                    if not item or not self:canPlaceMoveableInternal( _character, gridMember.square, item ) then
                        return false;
                    end
                    items[i] = {item, container};
                end
                for i,gridMember in ipairs(sgrid) do
                    local item, inventory = items[i][1], items[i][2];
                    self:placeMoveableInternal(  gridMember.square, item, gridMember.sprite:getName() );

                    if inventory=="floor" then
                        if item:getWorldItem() ~= nil then
                            item:getWorldItem():getSquare():transmitRemoveItemFromSquare(item:getWorldItem());
                            item:getWorldItem():getSquare():removeWorldObject(item:getWorldItem());
                            item:setWorldItem(nil);
                        end
                    else
                        inventory:Remove(item);
                    end
                    --items[i][2]:Remove(items[i][1]);
                    --_character:getInventory():Remove(items[i]);
                end
            else
                --if self.sprite == spriteGrid:getAnchorSprite() then
                    local item = self:findInInventoryMultiSprite( _character, self.name .. " (1/1)" ); --self:findInInventory( _character, spriteGrid:getAnchorSprite():getName() );
                    if item then
                        for i,gridMember in ipairs(sgrid) do
                            if not self:canPlaceMoveableInternal( _character, gridMember.square, item ) then
                                return false;
                            end
                        end
                        for i,gridMember in ipairs(sgrid) do
                            local gridItem = self:instanceItem(gridMember.sprite:getName());
                            if gridMember.sprite == spriteGrid:getAnchorSprite() then
                                gridItem = item;
                            end
                            self:placeMoveableInternal(  gridMember.square, gridItem, gridMember.sprite:getName() )
                        end

                        _character:getInventory():Remove(item);
                    end
                --end
            end

            ISMoveableCursor.clearCacheForAllPlayers();
        else
            local item = self:findInInventory( _character, _origSpriteName );
            if item  and self:canPlaceMoveableInternal( _character, _square, item ) then
                self:placeMoveableInternal( _square, item, self.spriteName )
                _character:getInventory():Remove(item);
                ISMoveableCursor.clearCacheForAllPlayers();
            end
        end
    end
end

local function getNameAndNumFromSpriteName(name)
    local len = string.len(name)
    local num = nil
    local strName = nil
    for i = len, 1, -1 do
        local sub = string.sub(name, i, i)
        if sub == "_" then
            strName = string.sub(name, 1, i-1)
            num = tonumber(string.sub(name, i+1, len))
            break
        end
    end
    return strName, num
end

function ISMoveableSpriteProps:placeMoveableInternal( _square, _item, _spriteName )
    local obj;
    local north         = self.facing and (self.facing=="N" or self.facing=="S");
    --local hasTileFloor  = _square and _square:getFloor();
    local insertIndex   = _square:getObjects() and _square:getObjects():size();
    local removeList    = {}; -- table used to remove objects after place (due to insertIndex)
    local TileIsoObjectType = getSprite( _spriteName ):getType()

    -- Place under IsoWorldInventoryObjects
    if insertIndex and insertIndex > 0 then
        local objects = _square:getObjects()
        for i=objects:size(),1,-1 do
            if not instanceof(objects:get(i-1), "IsoWorldInventoryObject") then
                insertIndex = i
                break
            end
        end
    end

    if self.isoType == "IsoObject" and self.isoType ~= getSprite( _spriteName ):getProperties():Val("IsoType") and getSprite( _spriteName ):getProperties():Val("IsoType") ~= nil then
        self.isoType = getSprite( _spriteName ):getProperties():Val("IsoType")
    end

    self.container = getSprite( _spriteName ):getProperties():Is("container") and getSprite( _spriteName ):getProperties():Val("container") or nil

    if self.type=="WallOverlay" then
        local wall = self:getWallForFacing( _square, self.facing );
        if wall and (self.facing == "W" or self.facing == "N") then
            -- The actual wall is on the adjacent square to the east or south.
            obj = IsoObject.new( getCell(), _square, getSprite(_spriteName) );
        elseif wall then
            wall:AttachExistingAnim(getSprite(_spriteName), 0, 0, false, 0, false, 0)
            --[[
            local overlay = getSprite(self.spriteName):newInstance();
            local sprList = wall:getChildSprites() or ArrayList.new();
            sprList:add( overlay );
            wall:setChildSprites( sprList );
            --]]
            if isClient() then wall:transmitUpdatedSpriteToServer() end
        end
    elseif self.type=="FloorTile" then
        local spr = getSprite( _spriteName ); --item:getWorldSprite() );
        local floor = _square:getFloor();
        if floor and spr then
            floor:setSprite(spr);
            if isClient() then floor:transmitUpdatedSpriteToServer(); end
        end
    elseif TileIsoObjectType == IsoObjectType.tree then
        local temp = IsoTree.new(_square, getSprite(_spriteName))
        _square:AddTileObject(temp)
        if isClient() then temp:transmitCompleteItemToServer(); end
        triggerEvent("OnObjectAdded", temp)
    elseif self.type=="Window" then
        obj = IsoWindow.new( getCell(), _square, getSprite( _spriteName ), north );
        obj:setIsLocked(false)

        local wallFrame = self:getWallForFacing( _square, north and "S" or "E", "WindowFrame" );
        local objects = _square:getObjects();
        for i=0, objects:size()-1 do
            local object 		= objects:get(i);
            local sprite 		= object:getSprite();

            if sprite and sprite:getName() then
                if instanceof(object, "IsoWindow") then
                    if ((not north) and sprite:getName() == "walls_special_01_8") or (north and sprite:getName() == "walls_special_01_9") then
                        table.insert( removeList, object );
                    end
                end
                if object == wallFrame then
                    insertIndex = i+1; --insert window after wallframe
                    if instanceof(object, "IsoThumpable") then
                        if string.sub(sprite:getName(),1,string.len("walls_exterior_wooden"))=="walls_exterior_wooden" then
                            object:setHoppable(false);
                        end
                    end
                end
            end
        end
    elseif _spriteName == "appliances_misc_01_0" then
        local genItem = InventoryItemFactory.CreateItem("Base.Generator")
        obj = IsoGenerator.new(genItem, getCell(), _square)
    elseif self.type=="WindowObject" then
        obj = IsoCurtain.new( getCell(), _square, _spriteName, north );
        if self:hasFaces() and (self.facing == "S" or self.facing == "E") then          -- If facing south or east, make sure window object is placed directly after window or windowFrame
            local wallFrame = self:getWallForFacing( _square, north and "S" or "E", "WindowFrame" );
            local window    = _square:getWindow(north);

            local objects = _square:getObjects();
            for i=0, objects:size()-1 do
                local object 		= objects:get(i);

                if (window and object == window) or (wallFrame and object == wallFrame) then
                    insertIndex = i+1;
                end
            end
        end
    else
        local itemSprite = _spriteName;
        local sprite = getSprite( itemSprite );
        local props = sprite and sprite:getProperties();
        local currentSurface = self:getTotalTableHeight(_square);
        if self.isMoveable and self.isTableTop and (not self.ignoreSurfaceSnap) then -- and self.facing ~= nil then		-- face correction when possible for items that are supposed to be on tables (like sinks)
            local faces = self:getFaces();

            local objects = _square:getObjects();
            for i=0, objects:size()-1 do
                local object 		= objects:get(i);
                local spr 			= object:getSprite();
                local mprops 		= ISMoveableSpriteProps.new( spr );
                if self.facing ~= nil and mprops.isMoveable and mprops.isTable and mprops:hasFaces() then
                    insertIndex = i+1;
                    local tmpSprite = self:getFaceSpriteFromParentObject( object );
                    if tmpSprite then
                        itemSprite = tmpSprite;
                        break;
                    end
                end
            end
        end

        local doDestroyAble = false;
        if self.isoType == "IsoBarbecue" then
            local bbqSprite = getSprite(itemSprite);
            if bbqSprite then
                obj = IsoBarbecue.new( getCell(), _square, bbqSprite );
                obj:setMovedThumpable(true);
            end
        elseif self.isoType == "IsoBrokenGlass" then
            obj = IsoBrokenGlass.new(getCell())
            obj:setSquare(_square);
        elseif self.isoType == "IsoCombinationWasherDryer" then
            obj = IsoCombinationWasherDryer.new(getCell(), _square, getSprite(itemSprite))
            obj:setMovedThumpable(true);
        elseif self.isoType == "IsoClothingDryer" then
            obj = IsoClothingDryer.new(getCell(), _square, getSprite(itemSprite))
            obj:setMovedThumpable(true);
        elseif self.isoType == "IsoClothingWasher" then
            obj = IsoClothingWasher.new(getCell(), _square, getSprite(itemSprite))
            obj:setMovedThumpable(true);
        elseif self.isoType == "IsoCompost" then
            obj = IsoCompost.new(getCell(), _square, getSprite(itemSprite))
            obj:setMovedThumpable(true);
        elseif self.isoType == "IsoMannequin" then
            obj = IsoMannequin.new(getCell(), _square, getSprite(itemSprite))
            obj:setSquare(_square)
            obj:getCustomSettingsFromItem(_item)
            if self.cursorFacing then
                local facing = { "N", "W", "S", "E" }
                local dir = IsoDirections[facing[self.cursorFacing]]
                obj:setDir(dir)
            end
        elseif self.isoType == "IsoRadio" or self.isoType == "IsoTelevision" then
            local oobj = nil
            if instanceof(_item,"Radio") then
                if self.isoType == "IsoRadio" then
                    obj = IsoRadio.new( getCell(), _square, getSprite(itemSprite) );
                    --oobj:setMovedThumpable(true);
                elseif self.isoType == "IsoTelevision" then
                    obj = IsoTelevision.new( getCell(), _square, getSprite(itemSprite) );
                    --oobj:setMovedThumpable(true);
                end
                local deviceData = _item:getDeviceData();
                if deviceData then
                    if deviceData.setIsTurnedOn ~= nil then
                        deviceData:setIsTurnedOn(deviceData:getIsTurnedOn()); --checks if current placing allows turnedon state (tv cant be turned on outside housing)
                    end
                    obj:setDeviceData(deviceData);
                else
                    print("Warning: device data missing?>?")
                end
                --[[
                if obj ~= nil then
                    _square:AddTileObject(obj)
                    if isClient() then obj:transmitCompleteItemToServer(); end
                    triggerEvent("OnObjectAdded", obj)
                end
                ]]--
            else
                if self.isoType == "IsoRadio" then
                    obj = IsoRadio.new( getCell(), _square, getSprite(itemSprite) );
                    obj:setMovedThumpable(true);
                elseif self.isoType == "IsoTelevision" then
                    obj = IsoTelevision.new( getCell(), _square, getSprite(itemSprite) );
                    obj:setMovedThumpable(true);
                end
                --[[
                if obj ~= nil then
                    _square:AddTileObject(obj)
                    if isClient() then obj:transmitCompleteItemToServer(); end
                    triggerEvent("OnObjectAdded", obj)
                end
                ]]--
            end

        elseif self.isoType == "IsoJukebox" then
            obj = IsoJukebox.new( getCell(), _square, getSprite(itemSprite) );
            obj:setMovedThumpable(true);
        elseif self.isoType == "IsoStove" then
            obj = IsoStove.new( getCell(), _square, getSprite(itemSprite) );
            obj:setMovedThumpable(true);
            --doDestroyAble = true;
        elseif self.isoType == "IsoFireplace" or self.container == "fireplace" then
            obj = IsoFireplace.new( getCell(), _square, getSprite(itemSprite) );
            print("DUCK")
        elseif self.isoType == "IsoMultiMedia" then
            obj = IsoMultiMedia.new( getCell(), _square, getSprite(itemSprite) );
        else
            local blockStyleSolid = props and (props:Is(IsoFlagType.solid) or props:Is(IsoFlagType.solidtrans)) or false;

            if props and TileIsoObjectType == IsoObjectType.lightswitch then
                if props:Is("streetlight") then
                    createTile(_spriteName, _square)
                    getPlayer():setHaloNote("Light tiles will be updated when zone will reloaded")
                else
                    obj = IsoLightSwitch.new( getCell(), _square, getSprite(_spriteName), _square:getRoomID() );
                    obj:addLightSourceFromSprite();
                    obj:getCustomSettingsFromItem(_item);
                end
            elseif not blockStyleSolid or self.type == "FloorRug" or self.type == "WallObject" or self.isTableTop then
                if props then
                    if props:Is(IsoFlagType.doorN) or props:Is(IsoFlagType.doorW) then
                        local tname, tnum = getNameAndNumFromSpriteName(itemSprite)
                        obj = IsoDoor.new(getCell(), _square, tname .. "_" .. tostring(tnum), props:Is(IsoFlagType.doorN));
                    elseif props:Is(IsoFlagType.WallN) or props:Is(IsoFlagType.WallW) then
                        obj = IsoThumpable.new(getCell(), _square, itemSprite, props:Is(IsoFlagType.WallN), {})
                        print(props:Is("GarageDoor"))
                    elseif props:Is(IsoFlagType.WindowN) or props:Is(IsoFlagType.WindowW) then
                        obj = IsoWindow.new( getCell(), _square, getSprite( _spriteName ), props:Is(IsoFlagType.WindowN) );
                        obj:setIsLocked(false)
                    else
                        obj = IsoObject.new( getCell(), _square, itemSprite );
                    end
                else
                    obj = IsoObject.new( getCell(), _square, itemSprite );
                    -- FIXME: This was used for the POLICE station sign.  But it interfers with 3D-item placement on shelves.
                end
            else
                if luautils.stringStarts(_spriteName, 'blends_natural_02') then
                    if luautils.stringStarts(_spriteName, 'blends_natural_02_0') or luautils.stringStarts(_spriteName, 'blends_natural_02_5') or luautils.stringStarts(_spriteName, 'blends_natural_02_6') or luautils.stringStarts(_spriteName, 'blends_natural_02_7') then
                        local spr = getSprite( _spriteName );
                        local floor = _square:getFloor();
                        if floor and spr then
                            for i=_square:getObjects():size()-1, 0, -1 do
                                local object = _square:getObjects():get(i)
                                if object ~= floor then
                                    if isClient() then _square:transmitRemoveItemFromSquare(object) end
                                    _square:RemoveTileObject(object);
                                end
                            end
                            floor:clearAttachedAnimSprite()
                            _square:addFloor(itemSprite)
                        end
                        getPlayer():setHaloNote("Tiles around water will improve when zone will reloaded")
                    else
                        getPlayer():setHaloNote("This water tiles not work because need water zone for it")
                        return
                    end
                else
                    obj = IsoThumpable.new(getCell(), _square, itemSprite, false, {}); --IsoObject.new( getCell(), self.square, itemSprite );
                    obj:setMaxHealth(self:getObjectHealth());
                    obj:setHealth(obj:getMaxHealth());
                    obj:setThumpDmg(1); --zeds needed to hurt obj
                    obj:setIsThumpable(true);
                    obj:setBlockAllTheSquare(true);
                    obj:setCanPassThrough(false);
                    obj:setCanPassThrough(false);
                    obj:setHoppable(false);
                    obj:setBreakSound("BreakObject");
                    --isGenericThump = true;
                    if _item:hasModData() then
                        local modData = _item:getModData()
                        if type(modData.name) == "string" then
                            obj:setName(modData.name)
                        end
                        if tonumber(modData.health) and tonumber(modData.maxHealth) then
                            obj:setHealth(tonumber(modData.health))
                            obj:setMaxHealth(tonumber(modData.maxHealth))
                        end
                        if type(modData.thumpSound) == "string" then
                            obj:setThumpSound(modData.thumpSound)
                        end
                        if type(modData.modData) == "table" then
                            for key,value in pairs(modData.modData) do
                                obj:getModData()[key] = value
                            end
                        end
                        if type(modData.color) == "userdata" then
                            obj:setCustomColor(modData.color);
                        end
                    end
                end
            end
        end

        -- Inform players about some tiles
        if _spriteName == "camping_01_04" or _spriteName == "camping_01_05" or _spriteName == "camping_01_06" then
            getPlayer():setHaloNote("Tile will start work when zone will reloaded")
        end

        if obj and _item and _item:hasModData() and _item:getModData().movableData then
            obj:getModData().movableData = copyTable(_item:getModData().movableData);
        end

        --fix for radio vehicle parts having condition
        if obj and _item and _item:getConditionMax()>0 then
            obj:getModData().itemCondition = { value = _item:getCondition(), max = _item:getConditionMax() };
        end

        if obj and doDestroyAble then
            if instanceof(obj,"IsoThumpable") then
                obj:setMaxHealth(self:getObjectHealth());
                obj:setHealth(obj:getMaxHealth());
                obj:setThumpDmg(1); --zeds needed to hurt obj
                obj:setIsThumpable(true);
                obj:setBreakSound("BreakObject");
            end
        end

        if obj and self.isTableTop then             --adjust y render mod if needed.
            --s.surface s.surfaceIsOffset
            if self.surface and self.surfaceIsOffset then
                currentSurface = currentSurface - self.surface;
            end
            obj:setRenderYOffset(currentSurface);
            --print(currentSurface);
        end

        if obj and self.isTable then
            obj:setRenderYOffset(currentSurface);
        end

        --local sprite = getSprite( itemSprite );
        if props and obj then --sprite and obj then
            --local props = sprite:getProperties();
            obj:createContainersFromSpriteProperties()
            for i=1,obj:getContainerCount() do
                obj:getContainerByIndex(i-1):setExplored(true)
            end

            if props:Is(IsoFlagType.waterPiped) then -- the item need to be re-piped inside a house or player's build safehouse
				obj:getModData().canBeWaterPiped = true;
--                obj:setUsesExternalWaterSource(true);
            end

            if props:Is("waterAmount") and _spriteName ~= "camping_01_16" then
                --print("water place");
                obj:setWaterAmount(0); --set water to zero after moving
                if (not props:Is(IsoFlagType.waterPiped)) and _item and _item:hasModData() then
                    local modData = _item:getModData()
                    if modData.waterAmount and tonumber(modData.waterAmount) then
                        obj:setWaterAmount(tonumber(modData.waterAmount));
                        obj:getModData().waterAmount = tonumber(modData.waterAmount);
                        obj:getModData().taintedWater = modData.taintedWater;
                        --print("setting water amount "..tostring(modData.waterAmount));
                    end
                end
            end

            --if isGenericThump and props:Is("lightR") then
            --obj:createLightSource(10, 5, 5, 0, 0, nil, nil, _character);
            --end
        end
    end

    if self.type == "FloorRug" and _square:getObjects() then
        local objects = _square:getObjects()
        for i=objects:size(),1,-1 do
            local obj2 = objects:get(i-1)
            if obj2:getProperties() and obj2:getProperties():Is(IsoFlagType.solidfloor) then
                insertIndex = i
                break
            end
        end
    end

    if _square:getObjects() and insertIndex > _square:getObjects():size() then
        insertIndex = _square:getObjects():size();
    end

    if obj then
        _square:AddSpecialObject( obj, insertIndex );
        if isClient() then obj:transmitCompleteItemToServer(); end
        triggerEvent("OnObjectAdded", obj) -- for RainCollectorBarrel in singleplayer
    end

    for _,remObject in ipairs(removeList) do
        --print("removing dummy window");
        if isClient() then _square:transmitRemoveItemFromSquare(remObject) end
        _square:RemoveTileObject(remObject);
    end

    getTileOverlays():fixTableTopOverlays(_square);

    _square:RecalcProperties();
    _square:RecalcAllWithNeighbours(true);
    --_character:getInventory():Remove(item);

    --ISMoveableCursor.clearCacheForAllPlayers();

    triggerEvent("OnContainerUpdate")

    IsoGenerator.updateGenerator(_square)
end

-- returns index of snapface
function ISMoveableSpriteProps:snapFaceToSquare( _square )
    if self.isMoveable and self:hasFaces() then
        local faces = self:getFaces();
        if (self.type == "Object" or self.type == "Vegitation") and self.isTableTop then
            local tableObject = self:getSpecificMoveableObjectFromSquare( _square, "Table" );
            if tableObject then
                local sprite 		= tableObject:getSprite();
                if sprite then
                    local props = sprite:getProperties();
                    if props then
                        local parentFace = props:Is("Facing") and props:Val("Facing");
                        if not parentFace then
                            return;
                        elseif faces.S and parentFace == "S" then
                            return 3;
                        elseif faces.E and parentFace == "E" then
                            return 4;
                        elseif faces.N and parentFace == "N" then
                            return 1;
                        elseif faces.W and parentFace == "W" then
                            return 2;
                        end
                    end
                end
            end
        end
        if self.type == "WallObject" or self.type == "WallOverlay" or self.type == "WindowObject" then
            local wallSearchMode = (self.type == "WindowObject" and "WindowFrame" or nil);
            if self.allowDoorFrame then --self.name=="Exit Sign" then
                wallSearchMode = "WallAndDoor";
            end
            if faces.S and self:getWallForFacing( _square, "S", wallSearchMode ) then
                return 3;
            elseif faces.E and self:getWallForFacing( _square, "E", wallSearchMode ) then
                return 4;
            elseif faces.N and self:getWallForFacing( _square, "N", wallSearchMode ) then
                return 1;
            elseif faces.W and self:getWallForFacing( _square, "W", wallSearchMode ) then
                return 2;
            end
        end
        if self.type == "Window" then
            if self.facing == "N" or self.facing == "W" then        -- windows only have N and W, but search tile on facing S and E
                if faces.N and self:getWallForFacing( _square, "S", "WindowFrame" ) then
                    return 1;
                elseif faces.W and self:getWallForFacing( _square, "E", "WindowFrame" ) then
                    return 2;
                end
            end
        end
    end
end

local wallModeTable = {
    Wall            = { N = {"WallN", "WallNW"}, W = {"WallW", "WallNW"} },
    WallAndDoor     = { N = {"WallN", "WallNW","DoorWallN"}, W = {"WallW", "WallNW","DoorWallW"} },
    WindowFrame     = { N = {IsoFlagType.WindowN}, W = {IsoFlagType.WindowW} },
}
-- gets a Wall for the defined facing (N S E W)
-- optionally can retrieve windowframes
function ISMoveableSpriteProps:getWallForFacing( _square, _dir, _mode )
    if not _dir then return; end
    if _dir == "N" then --object facing north, needs wall on south square
        _square = _square and _square:getTileInDirection(IsoDirections.S);
    elseif _dir == "W" then --object facing west, needs wall on east square
        _square = _square and _square:getTileInDirection(IsoDirections.E);
    end

    local lookup = wallModeTable.Wall;
    if _mode and _mode ~= "Wall" and wallModeTable[_mode] then
        lookup = wallModeTable[_mode];
    end

    local square, tag1, tag2, tag3;
    if _dir == "S" or _dir == "N" then
        square, tag1, tag2, tag3 = _square, lookup.N[1], lookup.N[2], lookup.N[3];
    elseif _dir == "E" or _dir == "W" then
        square, tag1, tag2, tag3 = _square, lookup.W[1], lookup.W[2], lookup.W[3];
    end

    if square and (tag1 or tag2 or tag3) then
        if (tag1 and square:Is(tag1)) or (tag2 and square:Is(tag2)) or (tag3 and square:Is(tag3)) then
            for i = 0, square:getObjects():size()-1 do
                local obj = square:getObjects():get(i);
                local sprite = obj:getSprite();
                if sprite and sprite:getProperties() then
                    local props = sprite:getProperties();
                    if props then
                        if (tag1 and props:Is(tag1)) or (tag2 and props:Is(tag2)) or (tag3 and props:Is(tag3)) then
                            return obj;
                        end
                    end
                end
            end
        end
    end
end

-- retrieve a overlay moveable from a object
function ISMoveableSpriteProps:getMoveableOverlayFromObject( _object )
    if _object then
        local sprList = _object:getChildSprites();
        if sprList then
            local list_size 	= sprList:size();
            if list_size > 0 then
                for i=list_size-1, 0, -1 do
                    local sprite = sprList:get(i):getParentSprite();
                    if sprite and sprite:getProperties() then
                        local props = sprite:getProperties();
                        if props then
                            if props:Is("IsMoveAble") and props:Is("MoveType") and props:Val("MoveType") == "WallObject" and props:Is(IsoFlagType.WallOverlay) then
                                return sprList:get(i), sprite;
                            end
                        end
                    end
                end
            end
        end
    end
end

-- get specific moveables from the square
function ISMoveableSpriteProps:getSpecificMoveableObjectFromSquare( _square, _objectType )
    if _square and _objectType then
        local objects   = _square:getObjects();
        for i=0, objects:size()-1 do
            local object 		= objects:get(i);
            local sprite 		= object:getSprite();
            if sprite then
                local props = sprite:getProperties();
                if props then
                    if _objectType == "Table" and props:Is("IsTable") then
                        return object;
                    elseif _objectType == "TableTop" and props:Is("IsTableTop") then
                        return object;
                    elseif _objectType == "WallObject" and props:Is("MoveType") and props:Val("MoveType") == "WallObject" then
                        return object;
                    end
                end
            end
        end
    end
end

-- returns the face from a table on square if possible
function ISMoveableSpriteProps:getFaceSpriteFromParentObject( _object )
    if _object and self.isMoveable and self:hasFaces() then
        local ownFaces      = self:getFaces();
        local sprite 		= _object:getSprite();
        if sprite then
            local props = sprite:getProperties();
            if props then
                if props:Is("Facing") and props:Val("Facing") then
                    local parentFacing = props:Val("Facing");
                    local newSprite = nil;
                    if parentFacing=="N" and ownFaces.N then
                        newSprite = ownFaces.N;
                    elseif parentFacing=="W" and ownFaces.W then
                        newSprite = ownFaces.W;
                    elseif parentFacing=="S" and ownFaces.S then
                        newSprite = ownFaces.S;
                    elseif parentFacing=="E" and ownFaces.E then
                        newSprite = ownFaces.E;
                    elseif (parentFacing=="N" or parentFacing=="S") and (ownFaces.N or ownFaces.S) then
                        newSprite = ownFaces.N or ownFaces.S;
                    elseif (parentFacing=="W" or parentFacing=="E") and (ownFaces.W or ownFaces.E) then
                        newSprite = ownFaces.W or ownFaces.E;
                    end

                    return newSprite;
                end
            end
        end
    end
end

-- returns of the current object can be rotated with the Rotate mode (some objects can be rotated during placement such as wall objects but cannot be rotated via the Rotate mode)
function ISMoveableSpriteProps:canManuallyRotate()
    if self.isMoveable and self:hasFaces() and (self.type == "Object" or self.type == "Vegitation") then
        return true;
    end
    if self:canRotateDirection() then
        return true;
    end
    return false;
end

-- return if this object's IsoDirection can be changed via rotation (an not by changing sprites)
function ISMoveableSpriteProps:canRotateDirection()
    if self.isMoveable and self.isoType == "IsoMannequin" then
        return true;
    end
    return false;
end

-- the current properties (self) should be the target rotation. _origProps are the properties of the object in the world.
function ISMoveableSpriteProps:canRotateMoveable( _square, _object, _origProps )
    if self.isMoveable then
        if self.isMultiSprite then
            if _origProps and _origProps.isMoveable and _origProps.isMultiSprite and self:canRotateMoveableInternal( _square, nil ) then
                local origGrid = _origProps:getSpriteGridInfo(_square, true); -- get the original's grid with the squares, objects etc retrieved.
                if origGrid and #origGrid>0 then
                    local sgrid = self:getSpriteGridInfo(origGrid[1].square, false); -- get the grid of the target rotation, this will have squares but no objects. Its origin is based on the origGrid anchor [1].
                    if not sgrid then return false; end

                    local square = origGrid[1].square
                    if self:isWallBetweenParts(self.sprite:getSpriteGrid(), square:getX(), square:getY(), square:getZ()) then
                        return false
                    end

                    for _,origMember in ipairs(origGrid) do --check if there are no tabletops on the original grid and if the objects are no containers or empty.
                        if origMember.square:Is("IsTableTop") then
                            return false;
                        end
                        if not self:objectNoContainerOrEmpty( origMember.object ) then
                            return false;
                        end
                    end
                    for _,gridMember in ipairs(sgrid) do
                        local coveredByOrig = false;
                        for _,origMember in ipairs(origGrid) do -- check if this position is covered by the original grid, if so it can be assumed that the current object can be placed in its new position.
                            if gridMember.square == origMember.square then
                                coveredByOrig = true;
                            end
                        end
                        if not coveredByOrig then
                            if not self:canPlaceMoveableInternal( nil, gridMember.square, nil ) then --if tile not covered by original then perform a place check.
                                return false;
                            end
                        end
                    end
                    return true;
                end
            end
        else
            return self:canRotateMoveableInternal( _square, _object );
        end
    end
    return false;
end

-- checks if the current rotation can be applied (current rotation should be 'self')
function ISMoveableSpriteProps:canRotateMoveableInternal( _square, _object )
    self.yOffsetCursor = _object and _object:getRenderYOffset() or 0;
    if self.isMoveable and self:hasFaces() then
        if _object and not self:objectNoContainerOrEmpty( _object ) then
            return false;
        end
        if self.type == "Object" or self.type == "Vegitation" then
            return true;
        end
    end
    if self:canRotateDirection() then
        return true
    end
    return false;
end

function ISMoveableSpriteProps:rotateMoveableViaCursor( _character, _square, _origSpriteName, _moveCursor )
    self:rotateMoveable(_character, _square, _origSpriteName);
    if _moveCursor then
        _moveCursor:clearCache()
    end
end

function ISMoveableSpriteProps:rotateMoveable( _character, _square, _origSpriteName )
    if self.isMoveable then
        if self.isMultiSprite then
            local origProps = ISMoveableSpriteProps.new( _origSpriteName ); -- get original.
            local origGrid = origProps:getSpriteGridInfo(_square, true);
            if origGrid and #origGrid>0 then
                local anchorSquare = origGrid[1].square;

                local items = origProps:pickUpMoveable( _character, _square, false, true ); -- pickup orignal. FIXME get a list of temporary items for placeMovable

                -- now place rotation based on anchor square
                local sgrid = self:getSpriteGridInfo(origGrid[1].square, false); --_square, false);
                for i,gridMember in ipairs(sgrid) do
                    self:placeMoveableInternal(  gridMember.square, items[i], gridMember.sprite:getName() )
                end
                ISMoveableCursor.clearCacheForAllPlayers();
            end
        else
            self:rotateMoveableInternal( _character, _square, _origSpriteName );
        end
    end
end

-- applies the rotation after action.
function ISMoveableSpriteProps:rotateMoveableInternal( _character, _square, _origSpriteName )
    if not self.isMoveable then return end
    if not instanceof(_character, "IsoGameCharacter") then return end
    if not instanceof(_square, "IsoGridSquare") then return end
    
    if self:hasFaces() then
        local obj, sprInstance = self:findOnSquare( _square, _origSpriteName );

        if obj and not sprInstance and self.sprite and self:canRotateMoveable( _square, obj ) then
            obj:setSprite( self.sprite );
            obj:RemoveAttachedAnims();
            if isClient() then obj:transmitUpdatedSpriteToServer(); end

            if self.isTable then                                                                                    -- If table then rotate tabletop items (sinks etc) to match table face.
                local tableTopObject = self:getSpecificMoveableObjectFromSquare( _square, "TableTop" );
                if tableTopObject then
                    local sprite 		= tableTopObject:getSprite();
                    local mprops 		= ISMoveableSpriteProps.new( sprite );
                    if mprops.isMoveable and mprops.isTableTop and mprops:hasFaces() then
                        local newSpriteName = mprops:getFaceSpriteFromParentObject( obj );
                        if newSpriteName then
                            local newSprite = getSprite(newSpriteName);
                            if newSprite then
                                tableTopObject:setSprite( newSprite );
                                tableTopObject:RemoveAttachedAnims();
                                if isClient() then tableTopObject:transmitUpdatedSpriteToServer(); end
                            end
                        end
                    end
                end
            end
            ISMoveableCursor.clearCacheForAllPlayers();
        end
    end

    if self.cursorFacing and self:canRotateDirection() then
        local obj, sprInstance = self:findOnSquare( _square, self.spriteName )
        if obj and not sprInstance and self.sprite and self:canRotateMoveable( _square, obj ) then
            local facing = { "N", "W", "S", "E" }
            local dir = IsoDirections[facing[self.cursorFacing]]

            local index = obj:getObjectIndex()
            local args = { x=obj:getX(), y=obj:getY(), z=obj:getZ(), index=index, dir=dir }
            sendClientCommand('object', 'rotate', args)

            ISMoveableCursor.clearCacheForAllPlayers()
        end
    end
end

function ISMoveableSpriteProps:walkToAndEquip( _character, _square, _mode )
    local dowalk = false;
    if self.type == "Window" or self.type == "WindowObject" then
        local dir = self.facing;
        if self.type == "Window" then
            local isNorth = self.facing and (self.facing == "N" or self.facing == "S") or false;
            dir = isNorth and "S" or "E";
        end
        local windowFrame = self:getWallForFacing( _square, dir, "WindowFrame" );
        dowalk = windowFrame and luautils.walkAdjWindowOrDoor( _character, _square, windowFrame, false);
    else
        dowalk = luautils.walkAdj( _character, _square, false );
    end
    if dowalk and _mode == "scrap" then
        local tool = self:hasScrapTool(_character, false)
        if tool == false or tool == nil then return false end
        if tool ~= true then	
			if tool:getType() == "BlowTorch" then
				ISWorldObjectContextMenu.equip(_character, _character:getPrimaryHandItem(), predicateBlowTorch, true)
			else
				ISWorldObjectContextMenu.equip(_character, _character:getPrimaryHandItem(), tool:getType(), true)
			end
        end
        local tool2 = self:hasScrapTool(_character, true)
        if tool2 == false or tool2 == nil then return false end
        if tool2 ~= true then
            if instanceof(tool2, "Clothing") then
                -- WeldingMask
                if not _character:isEquippedClothing(tool2) then
                    ISInventoryPaneContextMenu.wearItem(tool2, _character:getPlayerNum())
                end
            else
                ISWorldObjectContextMenu.equip(_character, _character:getSecondaryHandItem(), tool2:getType(), false)
            end
        end
        return true
    end
    if dowalk and _mode ~= "scrap" then
        local usesTool = (_mode == "pickup" and self.pickUpTool) or (_mode == "place" and self.placeTool);
        if not usesTool then
            return true;
        else
            local tool = self:hasTool( _character, _mode );

            if tool then
                ISWorldObjectContextMenu.equip(_character, _character:getPrimaryHandItem(), tool:getType(), true);
                return true;
            end
        end
    end
    return false;
end

-- sounds
--ISMoveableSpriteProps.toolDefinitions[_name] = { items = _items, perk = _perk, baseActionTime = _baseActionTime, sound = _sound, isWav = _isWav };
function ISMoveableSpriteProps:getSoundFromTool( _square, _character, _mode )
    if self.isMoveable and _square and _character and _mode then
        local tool = (_mode == "rotate" and "SpecialRotate") or (_mode == "pickup" and self.pickUpTool) or (_mode == "place" and self.placeTool);
        if tool then
            if tool == "SpecialRotate" then
                addSound(_character, _character:getX(), _character:getY(), _character:getZ(), 10, 5);
--                local sound = getSoundManager():PlayWorldSound("RotateObject", _square, 0.2, 20, 1.0, 6, true);
--                return sound;
                return _character:playSound("RotateObject")
            else
                local toolDef = ISMoveableDefinitions:getInstance().getToolDefinition( tool ); --ISMoveableSpriteProps.toolDefinitions[tool];
                if toolDef then
                    if toolDef.isWav then
--                        return getSoundManager():PlayWorldSoundWav(toolDef.sound, _character:getCurrentSquare(), 0, 15, 2, true);
                        return _character:playSound(toolDef.sound)
                    else
                        addSound(_character, _character:getX(), _character:getY(), _character:getZ(), 10, 5);
--                        local sound = getSoundManager():PlayWorldSound(toolDef.sound, _square, 0.2, 20, 1.0, 3, true);
--                        return sound;
                        return _character:playSound(toolDef.sound)
                    end
                end
            end
        end
    end
end

-- ###################################################################################################
-- SCRAP STUFF
-- ###################################################################################################

function ISMoveableSpriteProps:getScrapToolUses()
    return self.scrapToolUses and self.scrapToolUses or 0;
end

function ISMoveableSpriteProps:scrapWalkToAndEquip( _character )
    if self.canScrap and self.isFromObject and self.object:getSquare() then
        local square = self.object:getSquare();
        local dowalk = luautils.walkAdj( _character, square, false );
        if dowalk then
            if not self.scrapUseTool then
                return true;
            else
                local tool = self:hasScrapTool( _character );
                local tool2 = self:hasScrapTool( _character, true );

                if tool and tool2 then
                    ISWorldObjectContextMenu.equip(_character, _character:getPrimaryHandItem(), tool:getType(), true);
                    --print("tool2 = "..tostring(tool2)..", type = "..tostring(type(tool2)));
                    if type(tool2)~="boolean" then --if tool2 is an item
                        --print("attempt to equip");
                        ISWorldObjectContextMenu.equip(_character, _character:getSecondaryHandItem(), tool2:getType(), false);
                    end
                    return true;
                end
            end
        end
    end
end

--[[
function ISMoveableSpriteProps:addScrapItemToSquare( _square, _item, _max, _chance, _skillMod, _doSizeMod )
    local added = 0;
    if _square and _item and _max and _chance then
        if _skillMod and _skillMod > 0 and _skillMod <= 100 then
            _chance = (_chance/100)*_skillMod;
        end
        local amount = _max; --ZombRand(0,_max+1);
        if _doSizeMod then
            if self.scrapSize == "Small" then
                amount = amount/2 >= 1 and amount/2 or 1;
            elseif self.scrapSize == "Large" then
                amount = amount*2;
            end
        end
        if amount > 0 then
            for i = 1, amount do
                local randNum = ZombRandFloat(0,101);
                if randNum < _chance then
                    local item 	= instanceItem( _item );
                    if item then
                        if self.keyId and self.keyId ~= -1 then
                            if item:getType() == "Doorknob" then
                                item:setKeyId(self.keyId)
                            end
                        end
                        _square:AddWorldInventoryItem(item, ZombRandFloat(0.1,0.9), ZombRandFloat(0.1,0.9), 0);
                        added = added +1;
                    end
                end
            end
        end
    end
    return added;
end

function ISMoveableSpriteProps:spawnScrapItems( _square, _scrapDef, _skillChance )
    local added = 0;
    if _square and _scrapDef and _skillChance then
        if _scrapDef.returnItems and #_scrapDef.returnItems > 0 then
            for i,v in ipairs(_scrapDef.returnItems) do
                added = added + self:addScrapItemToSquare( _square, v.returnItem, v.maxAmount, v.chancePerRoll, _skillChance, true );
            end
        end
        if _scrapDef.returnItemsStatic and #_scrapDef.returnItemsStatic > 0 then
            for i,v in ipairs(_scrapDef.returnItemsStatic) do
                added = added + self:addScrapItemToSquare( _square, v.returnItem, v.maxAmount, v.chancePerRoll, _skillChance, false );
            end
        end
    end
    if added == 0 and _scrapDef.unusableItem then
        local rolls = ZombRand(1,3);
        for i = 1,rolls do
           _square:AddWorldInventoryItem(_scrapDef.unusableItem, ZombRandFloat(0.1,0.9), ZombRandFloat(0.1,0.9), 0);
        end
    end
    ISInventoryPage.dirtyUI()
    return added;
end
--]]

function ISMoveableSpriteProps:getScrapItemsList(_character)
    local materials = {};
    local items = { usable = {}, unusable = {} };
    if self.material then table.insert(materials, self.material); end
    if self.material2 then table.insert(materials, self.material2); end
    if self.material3 then table.insert(materials, self.material3); end

    if #materials<=0 then return end

    local scrapDef, chance, addedAmount;
    for k,mat in ipairs(materials) do
        scrapDef = ISMoveableDefinitions:getInstance().getScrapDefinition( mat );
        chance = self:getChanceByDef(scrapDef, _character);
        addedAmount = 0;
        if scrapDef and chance then
            if scrapDef.returnItems and #scrapDef.returnItems > 0 then
                for i,v in ipairs(scrapDef.returnItems) do
                    addedAmount = addedAmount + self:addScrapItemToList( items.usable, v.returnItem, v.maxAmount, v.chancePerRoll, chance, true );
                end
            end
            if scrapDef.returnItemsStatic and #scrapDef.returnItemsStatic > 0 then
                for i,v in ipairs(scrapDef.returnItemsStatic) do
                    addedAmount = addedAmount + self:addScrapItemToList( items.usable, v.returnItem, v.maxAmount, v.chancePerRoll, chance, false );
                end
            end

            if addedAmount == 0 and scrapDef.unusableItem then
                local rolls = ZombRand(1,3);
                for i = 1,rolls do
                    table.insert(items.unusable, scrapDef.unusableItem);
                end
            end
        end		
    end

    return items;
end

function ISMoveableSpriteProps:addScrapItemToList( _list, _item, _max, _chance, _skillMod, _doSizeMod )
    local added = 0;
    if _item and _max and _chance then
        if _skillMod and _skillMod > 0 and _skillMod <= 100 then
            _chance = (_chance/100)*_skillMod;
        end
        local amount = _max; --ZombRand(0,_max+1);
        if _doSizeMod then
            if self.scrapSize == "Small" then
                amount = amount/2 >= 1 and amount/2 or 1;
            elseif self.scrapSize == "Large" then
                amount = amount*2;
            end
        end
		print("_item " .. tostring(_item))
		-- kludge to prevent floors from providing more nails than used to make them
		if _item == "Base.Nails" and self.object:isFloor() and amount > 1 then
			amount = 1
		end
        if amount > 0 then
            for i = 1, amount do
                local randNum = ZombRandFloat(0,101);
                if randNum < _chance then
                    table.insert( _list, _item );
                    added = added +1;
                end
            end
        end
    end
    return added;
end

function ISMoveableSpriteProps:addOrDropItem( _character, _item )
	local inv = _character:getInventory()
	if not inv:contains(_item) then
		inv:AddItem(_item)
	end
	if inv:getCapacityWeight() > inv:getEffectiveCapacity(_character) then
		if inv:contains(_item) then
			inv:Remove(_item)
		end
		_character:getCurrentSquare():AddWorldInventoryItem(_item,
			_character:getX() - math.floor(_character:getX()),
			_character:getY() - math.floor(_character:getY()),
			_character:getZ() - math.floor(_character:getZ()))
	end
end

function ISMoveableSpriteProps:addAllScrapItemsToInventory( _character, _list )
    local added = 0;
    for k,v in ipairs(_list.usable) do
        local item = instanceItem( v );
        if item then
            if self.keyId and self.keyId ~= -1 then
                if item:getType() == "Doorknob" then
                    item:setKeyId(self.keyId)
                end
            end
            self:addOrDropItem(_character, item);
            added = added + 1;
        end
    end
    for k,v in ipairs(_list.unusable) do
        local item = instanceItem( v );
        if item then
            self:addOrDropItem(_character, item);
            --added = added + 1; unusable not counted for this
        end
    end
    ISInventoryPage.dirtyUI();
    return added;
end

function ISMoveableSpriteProps:addAllScrapItemsToSquare( _square, _list )
    local added = 0;
    for k,v in ipairs(_list.usable) do
        local item 	= instanceItem( v );
        if item then
            if self.keyId and self.keyId ~= -1 then
                if item:getType() == "Doorknob" then
                    item:setKeyId(self.keyId)
                end
            end
            _square:AddWorldInventoryItem(item, ZombRandFloat(0.1,0.9), ZombRandFloat(0.1,0.9), 0);
            added = added +1;
        end
    end
    for k,v in ipairs(_list.unusable) do
        local item 	= instanceItem( v );
        if item then
            _square:AddWorldInventoryItem(item, ZombRandFloat(0.1,0.9), ZombRandFloat(0.1,0.9), 0);
            --added = added +1; unusable not counted for this
        end
    end
    ISInventoryPage.dirtyUI();
    return added;
end

function ISMoveableSpriteProps:scrapObject(_character)
    local added = 0;
    local scrapResult, chance, perkName = self:canScrapObject(_character);
    local canScrap = scrapResult.canScrap;

    if canScrap then
        local scrapDef = ISMoveableDefinitions:getInstance().getScrapDefinition( self.material );
        local scrapResult, chance, perkName = self:canScrapObject(_character);

        if self.isMultiSprite then
            local grid = self:getSpriteGridInfo(self.object:getSquare(), true);
            if not grid or #grid<=0 then return false; end

            for i,member in ipairs(grid) do
                if scrapDef and member.object and member.square then
                    added = added + self:scrapObjectInternal(_character, scrapDef, member.square, member.object, scrapResult, chance, perkName);
                end
            end
        else
            local square = self.object and self.object:getSquare();
            if square and scrapDef then
                added = self:scrapObjectInternal(_character, scrapDef, square, self.object, scrapResult, chance, perkName);
            end
        end
    end

    --[[
    if added == 0 then
        _character:setHaloNote(getText("IGUI_Moveable_Fail"), 255,255,255,300);
    end
    --]]
    self:scrapHaloNoteCheck(_character, added)
end

-- also used in recipecode.lua
function ISMoveableSpriteProps:scrapHaloNoteCheck(_character, _itemsAdded)
    if _character then
        if _itemsAdded == 0 then
            _character:setHaloNote(getText("IGUI_Moveable_Fail"), 255,255,255,300);
        end
    end
end

function ISMoveableSpriteProps:scrapObjectInternal( _character, _scrapDef, _square, _object, _scrapResult, _chance, _perkName )
    local added = 0;
    local scrapResult, chance, perkName = _scrapResult, _chance, _perkName;
    local scrapDef = _scrapDef;
    local object = _object;
    local square = _square;
    if scrapDef and object and square then
        self.keyId = nil
        if instanceof(object, "IsoDoor") then
            self.keyId = object:checkKeyId()
        elseif instanceof(object, "IsoThumpable") then
            self.keyId = object:getKeyId()
        end

        -- Carpentry objects should return items from modData "need:", not MaterialN tile properties.
        -- i.e., Log Walls should give back sheets/rope/twine used to build it, not nails.
        if instanceof(object, "IsoThumpable") and object:hasModData() then
            scrapDef = copyTable(_scrapDef)
            scrapDef.returnItems = {}
            scrapDef.returnItemsStatic = {}
            for k,v in pairs(self.object:getModData()) do
                if luautils.stringStarts(k, "need:") then
                    local type = luautils.split(k, ":")[2]
                    local count = tonumber(v)
                    local item = { returnItem = type, maxAmount = count, chancePerRoll = 80 }
                    table.insert(scrapDef.returnItemsStatic, item)
                end
            end
--            self.scrapSize = nil
            self.material2 = nil
            self.material3 = nil
        end

        local deviceData = object.getDeviceData and object:getDeviceData();

        if object:isFloor() and (_square:getZ() == 0) then
            local floor = _square:getFloor();
            if floor then
                floor:setSpriteFromName("blends_natural_01_64");
                if isClient() then floor:transmitUpdatedSpriteToServer(); end
            end
        else
            if isClient() then square:transmitRemoveItemFromSquare(object) end
            square:RemoveTileObject(object);
            square:RecalcProperties();
            square:RecalcAllWithNeighbours(true);
        end

        if self.customItem then
            -- if the moveable object returns a custom item, check if the item can be dismantled
            -- if so, use the dismantle recipes/xp so they are similar.
            local recipe = RecipeManager.getDismantleRecipeFor(self.customItem);
            if recipe then
                local item = instanceItem(self.customItem);
                if item then
                    if deviceData and item.setDeviceData then
                        item:setDeviceData(deviceData);
                        --add item to inventory for the makeItem code for function:
                        _character:getInventory():AddItem(item);
                        RecipeManager.PerformMakeItem(recipe, item, _character, nil);
                        return 1;
                    end
                end
            end
        end

        --added = added + self:spawnScrapItems( square, scrapDef, chance );
        --[[
        -- give XP
        local multiplier = 1;
        if self.scrapSize == "Medium" then
            multiplier = 2;
        elseif self.scrapSize == "Large" then
            multiplier = 3;
        end
        _character:getXp():AddXP(scrapDef.perk, 5 * multiplier)
        --]]
        local items = self:getScrapItemsList(_character);

        if scrapDef.addToInventory then
            added = self:addAllScrapItemsToInventory( _character, items );
        else
            added = self:addAllScrapItemsToSquare( _square, items );
        end

        self:scrapGiveXp(_character, scrapDef);

        if _character:getPrimaryHandType() == "BlowTorch" then
            _character:getPrimaryHandItem():Use()
        end

        --[[
        if self.material2 then
            scrapDef = ISMoveableDefinitions:getInstance().getScrapDefinition( self.material2 );
            chance = self:getChanceByDef(scrapDef, _character);
            if scrapDef then
                added = added + self:spawnScrapItems( square, scrapDef, chance );
            end
        end
        if self.material3 then
            scrapDef = ISMoveableDefinitions:getInstance().getScrapDefinition( self.material3 );
            chance = self:getChanceByDef(scrapDef, _character);
            if scrapDef then
                added = added + self:spawnScrapItems( square, scrapDef, chance );
            end
        end
        --]]
    end
    return added;
end

-- also used in recipecode.lua
function ISMoveableSpriteProps:scrapGiveXp(_character, _scrapDef)
    if not _scrapDef then
        _scrapDef = ISMoveableDefinitions:getInstance().getScrapDefinition( self.material );
    end

    if _character and _scrapDef and _scrapDef.perk and (_scrapDef.perk ~= Perks.MAX) then
        -- give XP
        local multiplier = 1;
        if self.scrapSize == "Medium" then
            multiplier = 2;
        elseif self.scrapSize == "Large" then
            multiplier = 3;
        end
        _character:getXp():AddXP(_scrapDef.perk, 5 * multiplier)
    end
end

function ISMoveableSpriteProps:getChanceByDef(scrapDef, chr)
	if ISMoveableDefinitions.cheat then
		return 100;
	end
    local chance = 10 + chr:getPerkLevel(scrapDef.perk)*10;
    if scrapDef.baseChance then
        chance = chance + scrapDef.baseChance;
    end
    if chance < 0 then chance = 0 end
    if chance > 100 then chance = 100 end
    return chance;
end

function ISMoveableSpriteProps:canScrapObject(_character)
    local canScrap = false;
    local chance = 0;
    local perkName;
    local result = {};

    if self.canScrap and self.isFromObject and self.object:getSquare() and _character and instanceof(_character,"IsoGameCharacter") then
        if self.isMultiSprite then
            local grid = self:getSpriteGridInfo(self.object:getSquare(), true);
            if not grid or #grid<=0 then
                canScrap = false;
            else
                for i,member in ipairs(grid) do
                    canScrap = self:canScrapObjectInternal(result, member.object);
                    if not canScrap then
                        break;
                    end
                end
            end
        else
            canScrap = self:canScrapObjectInternal(result, self.object);
        end
        if self.material then
            result.craftValid = ISMoveableDefinitions:getInstance().isScrapDefinitionValid( self.material );
            canScrap = canScrap and result.craftValid;
        end

        if self.scrapUseTool then
            result.haveTool = self:hasScrapTool( _character );
            result.haveTool2 = self:hasScrapTool( _character, true );
            canScrap = canScrap and result.haveTool and result.haveTool2;
        end

        if canScrap then
            if self.scrapUseSkill then
                chance, perkName = self:getScrapSkillChance( _character );
            else
                chance = 100;
            end
        end
    end
	if ISMoveableDefinitions.cheat then
		canScrap = true;
		chance = 100;
	end
    result.canScrap = canScrap;
    return result, chance, perkName;
end

function ISMoveableSpriteProps:canScrapObjectInternal(_result, _object)
    self.yOffsetCursor = _object and _object:getRenderYOffset() or 0;
    if not _result.containerFull then
        _result.containerFull = not self:objectNoContainerOrEmpty( self.object );
    end
    local canScrap = not _object or self:objectNoContainerOrEmpty( _object );
    if canScrap and self.isTable then
        canScrap = not _object:getSquare():Is("IsTableTop") and _object == self:getTopTable(_object:getSquare());
    end
    if canScrap and instanceof(_object, "IsoDoor") and _object:isBarricaded() then
        canScrap = false
    end
    if canScrap and instanceof(_object, "IsoThumpable") and _object:isDoor() and _object:isBarricaded() then
        canScrap = false
    end
    if canScrap and instanceof(_object, "IsoThumpable") and _object:isDoorFrame() and _object:getSquare():getDoor(_object:getNorth()) then
        canScrap = false
    end
    if canScrap and instanceof(_object, "IsoThumpable") and _object:isWindow() and _object:getSquare():getWindow(_object:getNorth()) then
        canScrap = false
    end
    -- As with ISDestroyCursor, the floor at the top of a staircase cannot be destroyed.
    if canScrap and self:isFloorAtTopOfStairs(_object) then
        canScrap = false
    end
    if canScrap and self.isWaterCollector then
        if _object and _object:hasWater() then
            canScrap = false
        end
    end
    return canScrap;
end

function ISMoveableSpriteProps:isFloorAtTopOfStairs(object)
    if not object then return false end
    if object:getZ() == 0 then return false end
    local props = object:getProperties()
    if not props:Is(IsoFlagType.solidfloor) then return false end
    local square = getCell():getGridSquare(object:getX() + 1, object:getY(), object:getZ() - 1)
    if square and square:Has(IsoObjectType.stairsTW) then return true end
    square = getCell():getGridSquare(object:getX(), object:getY() + 1, object:getZ() - 1)
    if square and square:Has(IsoObjectType.stairsTN) then return true end
    return false
end

function ISMoveableSpriteProps:getScrapSkillChance( _player )
    local chance = 100;
    local perkName;
    if _player and self.canScrap then
        local scrapDef =  ISMoveableDefinitions:getInstance().getScrapDefinition( self.material );
        if scrapDef and scrapDef.perk then
            chance = 10 + _player:getPerkLevel(scrapDef.perk)*10;
            perkName = scrapDef.perkName;
            if scrapDef.baseChance then
                chance = chance + scrapDef.baseChance;
            end
        end
    end
    if chance < 1 then
        chance = 1;
    end
    if chance > 100 then chance = 100; end
    return chance, perkName;
end

-- return tool if found, true if no tools are defined for this material, false on fail.
function ISMoveableSpriteProps:hasScrapTool( _player, _doSecond )
    if _player then
        local inventory = _player:getInventory();
        local scrapDef =  ISMoveableDefinitions:getInstance().getScrapDefinition( self.material );
        if scrapDef then
            local tools = _doSecond and scrapDef.tools2 or scrapDef.tools;
            if tools then
                if #tools <=0 then return true; end --in case secondary toolset has no items.
                for _,v in ipairs(tools) do
                    if v == "Base.BlowTorch" then
                        local item = inventory:getFirstTypeEvalRecurse(v, function(it) return it:getUsedDelta() >= 0.1 end)
                        if item then
                            return item
                        end
                    else
                        local item = inventory:getFirstTypeRecurse(v);
                        if item then
                            return item;
                        end
                    end
                end
            end
        end
    end
    return false;
end

function ISMoveableSpriteProps:getScrapActionTime( _player )
    --if self.canScrap and self.isFromObject and self.object:getSquare() then
    if self.canScrap and _player then
        local scrapDef =  ISMoveableDefinitions:getInstance().getScrapDefinition( self.material );
        if scrapDef then
            local actionTime = scrapDef.baseActionTime;
            if scrapDef.perk then
                local perkLevel = _player:getPerkLevel(scrapDef.perk);
                actionTime = actionTime - (actionTime*(perkLevel*0.03));
            end
            return actionTime;
        end
    end
    --print("default action time");
    return 100;
end

function ISMoveableSpriteProps:getScrapSound( _character )
    if self.canScrap and self.isFromObject and self.object:getSquare() then
        local scrapDef =  ISMoveableDefinitions:getInstance().getScrapDefinition( self.material );
        if scrapDef then
            if scrapDef.isWav then
--                return getSoundManager():PlayWorldSoundWav(scrapDef.sound, _character:getCurrentSquare(), 0, 15, 2, true);
                return _character:playSound(scrapDef.sound)
            else
                addSound(_character, _character:getX(), _character:getY(), _character:getZ(), 10, 5);
                return _character:playSound(scrapDef.sound)
--                local sound = getSoundManager():PlayWorldSound(scrapDef.sound, self.object:getSquare(), 0.2, 20, 1.0, 3, true);
--                return sound;
            end
        end
    end
end

function ISMoveableSpriteProps:scrapObjectViaCursor( _character, _square, _origSpriteName, _moveCursor )
    self:scrapObject(_character);
    if _moveCursor then
        _moveCursor:clearCache()
    end
end

function ISMoveableSpriteProps:startScrapAction( _action )
    if self.canScrap then
        local scrapDef = ISMoveableDefinitions:getInstance().getScrapDefinition( self.material )
        if scrapDef then
            if scrapDef.recipeAnimNode then
                _action:setActionAnim(scrapDef.recipeAnimNode)
                _action:setOverrideHandModels(scrapDef.recipeProp1, scrapDef.recipeProp2)
                return true
            end
        end
    end
    return false
end

-- -- -- -- --
-- -- -- -- --
-- -- -- -- --

-- This is a lightweight version of ISMoveableSpriteProps for IsoThumpables that can be dismantled.
-- Possibly this can be removed if the IsoThumpable sprites had the appropriate properties.
ISThumpableSpriteProps = {}

function ISThumpableSpriteProps.new(object)
    local o = {}
    setmetatable(o, { __index = ISThumpableSpriteProps })
    o.sprite = object:getSprite()
    if o.sprite then
        o.spriteName = o.sprite:getName()
        o.canScrap = true
        o.scrapThumpable = true
        o.isFromObject = true
        o.object = object
        o.name = "Scrapable object"
        local props = o.sprite:getProperties()
        if props then
            o.groupName = props:Is("GroupName") and props:Val("GroupName") or nil
            o.customName = props:Is("CustomName") and props:Val("CustomName") or nil
            if o.groupName and o.customName then
                o.name = o.groupName .. " " .. o.customName
            elseif o.customName then
                o.name = o.customName
            elseif object:getName() and object:getName() ~= "" then
                o.name = object:getName()
            end
        end
    end
    return o
end

function ISThumpableSpriteProps:getYOffsetCursor()
    return 0
end

function ISThumpableSpriteProps:getInfoPanelDescription( _square, _object, _player, _mode )
    local infoTable = {};
    local bR,bG,bB = ISMoveableSpriteProps.bhc:getR()*255, ISMoveableSpriteProps.bhc:getG()*255, ISMoveableSpriteProps.bhc:getB()*255
    local gR,gG,gB = ISMoveableSpriteProps.ghc:getR()*255, ISMoveableSpriteProps.ghc:getG()*255, ISMoveableSpriteProps.ghc:getB()*255
    if _mode ~= "scrap" or not self.scrapThumpable then
        return ISMoveableSpriteProps.addLineToInfoTable( infoTable, getText("IGUI_NoCanScrap"), bR,bG,bB )
    end
    infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, getText("IGUI_Name")..":", 255, 255, 255, Translator.getMoveableDisplayName(self.name), gR,gG,gB )

    local skillText = PerkFactory.getPerkName(Perks.Woodwork)
    infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, getText("IGUI_Skill")..":", 255, 255, 255, skillText, getColorValues(true) )

    local hasTool = _player:getInventory():containsTagEvalRecurse("Saw", predicateNotBroken)
    infoTable = self:addToolString(infoTable, "Saw", hasTool)

    hasTool = _player:getInventory():containsTagEvalRecurse("Screwdriver", predicateNotBroken)
    infoTable = self:addToolString(infoTable, "Screwdriver", hasTool)

    return infoTable
end

function ISThumpableSpriteProps:addToolString(infoTable, tag, hasTool)
    local r,g,b = getColorValues(hasTool)
    local items = getScriptManager():getItemsTag(tag)
    if items:isEmpty() then
        infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, getText("IGUI_Tool")..":", 255, 255, 255, tag, r, g, b )
        return
    end
    local first = true
    for i=1,items:size() do
        local scriptItem = items:get(i-1)
        if first then
            infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, getText("IGUI_Tool")..":", 255, 255, 255, scriptItem:getDisplayName(), r, g, b )
        else
            infoTable = ISMoveableSpriteProps.addLineToInfoTable( infoTable, "", 255, 255, 255, scriptItem:getDisplayName(), r, g, b )
        end
        first = false
    end
    return infoTable
end

function ISThumpableSpriteProps:walkToAndEquip( _character, _square, _mode )
    if _mode ~= "scrap" then return false end
    if luautils.walkAdj(_character, _square, false) then
        local tool = _character:getInventory():getFirstTagEvalRecurse("Saw", predicateNotBroken)
        if not tool then return false end
        ISWorldObjectContextMenu.equip(_character, _character:getPrimaryHandItem(), tool:getType(), true)

        tool = _character:getInventory():getFirstTagEvalRecurse("Screwdriver", predicateNotBroken)
        if not tool then return false end
        ISWorldObjectContextMenu.equip(_character, _character:getSecondaryHandItem(), tool:getType(), false)
        return true
    end
    return false
end

function ISThumpableSpriteProps:getScrapActionTime( _character )
    return 200 - (_character:getPerkLevel(Perks.Strength) * 10)
end

function ISThumpableSpriteProps:getScrapSound( _character )
    if self.canScrap and self.isFromObject and self.object:getSquare() then
        addSound(_character, _character:getX(), _character:getY(), _character:getZ(), 10, 5)
--        local sound = getSoundManager():PlayWorldSound("PZ_Saw", self.object:getSquare(), 0.2, 20, 1.0, 3, true)
--        return sound
        return _character:playSound("Sawing")
    end
end

function ISThumpableSpriteProps:scrapObjectViaCursor( _character, _square, _origSpriteName, _moveCursor )
    -- This is from the old ISDismantleAction
    
    for k,v in pairs(self.object:getModData()) do
        if luautils.stringStarts(k, "need:") then
            local type = luautils.split(k, ":")[2]
            if type == "Base.Torch" then
                -- Big hack for ISLightSource
                self.object:toggleLightSource(false)
                local item = InventoryItemFactory.CreateItem(type)
                if item then
                    item:setUsedDelta(0)
                    self.object:getSquare():AddWorldInventoryItem(item, 0, 0, 0)
                end
                item = self.object:removeCurrentFuel(nil)
                if item then
                    self.object:getSquare():AddWorldInventoryItem(item, 0, 0, 0)
                end
            else
                local count = ZombRand(tonumber(v))+1
                for i=1,count do
                    self.object:getSquare():AddWorldInventoryItem(type, 0, 0, 0)
                end
            end
        end
    end

    if isClient() then
        local sq = self.object:getSquare()
        local args = { x = sq:getX(), y = sq:getY(), z = sq:getZ(), index = self.object:getObjectIndex() }
        sendClientCommand(_character, 'object', 'OnDestroyIsoThumpable', args)
    end

    -- Dismantle all 3 stair objects (and sometimes the floor at the top)
    local stairObjects = buildUtil.getStairObjects(self.object)
    if #stairObjects > 0 then
        for i=1,#stairObjects do
            stairObjects[i]:getSquare():transmitRemoveItemFromSquare(stairObjects[i])
        end
    else
        self.object:getSquare():transmitRemoveItemFromSquare(self.object)
    end

    ISInventoryPage.dirtyUI()

    if _moveCursor then
        _moveCursor:clearCache()
    end
end

function ISThumpableSpriteProps:canScrapObject(playerObj)
    local result = { canScrap = false }
    local chance = 0
    local perkName
    if self.scrapThumpable then
        result.canScrap = true
        chance = 10 + playerObj:getPerkLevel(Perks.Woodwork) * 10
        perkName = "Woodwork"
    end
    return result, chance, perkName
end

function ISThumpableSpriteProps:startScrapAction( _action )
    _action:setActionAnim("SawLog")
    _action:setOverrideHandModels(_action.character:getPrimaryHandItem(), nil)
    return true
end

--[[
-- Function that hooks to the creation dynamic recipes for movable inventory items
--]]

function ISMoveableSpriteProps.OnDynamicMovableRecipe(_sprite, _recipe, _item, _player)
    local props = ISMoveableSpriteProps.new( _sprite );
    --print("can scrap = "..tostring(props.canScrap))
    --print("item = "..tostring(_item:getFullType()));
    --[[
    props.scrapToolUses
    --]]
    if props.canScrap and (not props.isMultiSprite) and props.material then
        local scrapDef = ISMoveableDefinitions:getInstance().getScrapDefinition( props.material );

        --_recipe:setSource(_item:getFullType());
        _recipe:setTime( props:getScrapActionTime( _player ) );

        local tools = "";
        if scrapDef.tools and #scrapDef.tools>0 then
            for k,v in ipairs(scrapDef.tools) do
                tools = tools..v;
                if k<#scrapDef.tools then
                    tools = tools.."/";
                end
            end
            --print("tools = "..tools);
            _recipe:setTool(tools, true);
        end

        if scrapDef.tools2 and #scrapDef.tools2>0 then
            tools = "";
            for k,v in ipairs(scrapDef.tools2) do
                tools = tools..v;
                if k<#scrapDef.tools2 then
                    tools = tools.."/";
                end
            end
            --print("tools2 = "..tools);
            _recipe:setTool(tools, false);
        end

        _recipe:setSource(_item:getFullType());

        local items = scrapDef.returnItemsStatic or {}; --scrapDef.returnItems; --ISMoveableDefinitions:getInstance().getScrapItems( props.material );
        if (not items) or #items<1 then
            items = scrapDef.returnItems;
        end

        if items and #items>0 and items[1].returnItem then
            --print("return item = "..tostring(items[1].returnItem))
            _recipe:setResult(items[1].returnItem, 1);

            if _recipe:getResult():getModule() then
                local name = getText("IGUI_Scrap") .. " " .. _item:getDisplayName();
                _recipe:setName(name);
                _recipe:setWorldSprite(_item:getWorldSprite());
                if scrapDef.perk then
                    _recipe:setXpPerk(scrapDef.perk);
                end

                _recipe:setOnCreate("Recipe.OnCreate.DynamicMovable");
                _recipe:setOnXP("Recipe.OnGiveXP.DynamicMovable");

                if scrapDef.recipeAnimNode then
                    _recipe:setAnimNode(scrapDef.recipeAnimNode)
                end

                --fixme, currently setting screwdriver as default prop, might leave it like that as there are many variations in tools and objects being dismantled?
                if scrapDef.recipeProp1 then
                    _recipe:setProp1(scrapDef.recipeProp1);
                else
                    _recipe:setProp1("Screwdriver");
                end

                if scrapDef.recipeProp2 then
                    _recipe:setProp2(scrapDef.recipeProp2);
                end
                
                if scrapDef.recipeSound then
                    _recipe:setSound(scrapDef.recipeSound)
                end

                --print("recipe is correct")
                _recipe:setValid(true);
            end
        end
    end
end

Events.OnDynamicMovableRecipe.Add(ISMoveableSpriteProps.OnDynamicMovableRecipe);

